@ingcreators/annot-product-docs 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/dist/index.js ADDED
@@ -0,0 +1,860 @@
1
+ import { mkdir as e, readFile as t, readdir as n, writeFile as r } from "node:fs/promises";
2
+ import { dirname as i, join as a, relative as o, resolve as s } from "node:path";
3
+ import { test as c } from "@ingcreators/annot-playwright";
4
+ import l from "js-yaml";
5
+ import u from "remark-frontmatter";
6
+ import d from "remark-mdx";
7
+ import f from "remark-parse";
8
+ import { unified as p } from "unified";
9
+ import { visit as m } from "unist-util-visit";
10
+ import { z as h } from "zod";
11
+ //#region src/resolver.ts
12
+ function g(e) {
13
+ let t = [], n = e.split(/\r?\n/), r = [];
14
+ for (let e of n) {
15
+ if (!e.trim()) continue;
16
+ let n = e.length - e.replace(/^\s+/, "").length, i = Math.floor(n / 2), a = e.match(/^\s*-\s+([a-z]+)(?:\s+"([^"]*?)")?/);
17
+ if (!a) continue;
18
+ let o = a[1] ?? "", s = a[2] ?? "";
19
+ for (; r.length > 0 && (r[r.length - 1]?.depth ?? -1) >= i;) r.pop();
20
+ let c = r.map((e) => ({
21
+ role: e.role,
22
+ name: e.name
23
+ })), l = e.match(/\[ref=(e\d+)\]/);
24
+ l && t.push({
25
+ role: o,
26
+ name: s,
27
+ ref: l[1] ?? "",
28
+ depth: i,
29
+ ancestors: c
30
+ }), /:\s*$/.test(e) && r.push({
31
+ depth: i,
32
+ role: o,
33
+ name: s
34
+ });
35
+ }
36
+ return t;
37
+ }
38
+ async function _(e, t, n) {
39
+ let r = te(t.filter((t) => t.role === e.role && t.name === e.name), e.under);
40
+ if (r.length === 0) return ne(e, t);
41
+ if (r.length > 1) return {
42
+ ok: !1,
43
+ kind: "ambiguous",
44
+ reason: `Multiple elements match role="${e.role}" name="${e.name}". Use \`under\` to disambiguate.`,
45
+ candidates: r.map((e) => `[ref=${e.ref}]`)
46
+ };
47
+ let i = r[0], a = v(n, e), o = await a.count();
48
+ return o === 0 ? {
49
+ ok: !1,
50
+ kind: "live-mismatch",
51
+ reason: `Snapshot says element exists but live locator failed: role="${e.role}" name="${e.name}".`
52
+ } : o > 1 ? {
53
+ ok: !1,
54
+ kind: "ambiguous",
55
+ reason: `Live locator returns ${o} elements for role="${e.role}" name="${e.name}".`
56
+ } : {
57
+ ok: !0,
58
+ locator: a,
59
+ entry: i
60
+ };
61
+ }
62
+ async function ee(e, t, n) {
63
+ let r = [];
64
+ for (let i of e) r.push({
65
+ overlay: i,
66
+ result: await _(i.match, t, n)
67
+ });
68
+ return r;
69
+ }
70
+ function v(e, t) {
71
+ let n = e.getByRole(t.role, {
72
+ name: t.name,
73
+ exact: !0
74
+ });
75
+ return t.under && (n = e.getByRole(t.under.role, {
76
+ name: t.under.name,
77
+ exact: !0
78
+ }).getByRole(t.role, {
79
+ name: t.name,
80
+ exact: !0
81
+ })), n;
82
+ }
83
+ function te(e, t) {
84
+ return t ? e.filter((e) => e.ancestors.some((e) => e.role === t.role && e.name === t.name)) : e;
85
+ }
86
+ function ne(e, t) {
87
+ let n = t.filter((t) => t.name === e.name);
88
+ if (n.length === 1 && n[0].role !== e.role) return {
89
+ ok: !1,
90
+ kind: "role-changed",
91
+ reason: `Element with name="${e.name}" exists but has role="${n[0].role}", not "${e.role}".`,
92
+ suggestion: n[0].role
93
+ };
94
+ let r = t.filter((t) => t.role === e.role);
95
+ if (r.length > 0) {
96
+ let t = re(e.name, r.map((e) => e.name));
97
+ if (t) return {
98
+ ok: !1,
99
+ kind: "renamed",
100
+ reason: `No element with role="${e.role}" name="${e.name}". Closest match: name="${t}".`,
101
+ suggestion: t
102
+ };
103
+ }
104
+ return {
105
+ ok: !1,
106
+ kind: "not-found",
107
+ reason: `No element with role="${e.role}" name="${e.name}".`
108
+ };
109
+ }
110
+ function re(e, t) {
111
+ if (t.length === 0) return null;
112
+ let n = t[0], r = y(e, n);
113
+ for (let i of t.slice(1)) {
114
+ let t = y(e, i);
115
+ t > r && (n = i, r = t);
116
+ }
117
+ return r > .3 ? n : null;
118
+ }
119
+ function y(e, t) {
120
+ if (!e || !t) return 0;
121
+ let n = (e) => {
122
+ let t = /* @__PURE__ */ new Set();
123
+ for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
124
+ return t;
125
+ }, r = n(e), i = n(t);
126
+ if (r.size === 0 || i.size === 0) return 0;
127
+ let a = 0;
128
+ for (let e of r) i.has(e) && a++;
129
+ return a / Math.max(r.size, i.size);
130
+ }
131
+ //#endregion
132
+ //#region src/drift.ts
133
+ function b(e) {
134
+ let { screen: t, liveSnapshot: n, storedAttributesYaml: r, freshAttributesYaml: i } = e, a = [], o = /* @__PURE__ */ new Set();
135
+ for (let e of t.overlays) {
136
+ let r = n.filter((t) => t.role === e.match.role && t.name === e.match.name);
137
+ if (r.length > 1) {
138
+ a.push({
139
+ severity: "error",
140
+ kind: "duplicated",
141
+ screenId: t.id,
142
+ message: `Multiple elements match role="${e.match.role}" name="${e.match.name}". Use \`under\` to disambiguate.`,
143
+ match: e.match
144
+ });
145
+ continue;
146
+ }
147
+ if (r.length === 1) continue;
148
+ let i = n.filter((t) => t.name === e.match.name);
149
+ if (i.length === 1 && i[0].role !== e.match.role) {
150
+ let n = i[0];
151
+ o.add(`${n.role}|${n.name}`), a.push({
152
+ severity: "warning",
153
+ kind: "role-changed",
154
+ screenId: t.id,
155
+ message: `Element with name="${e.match.name}" exists but has role="${n.role}", not "${e.match.role}".`,
156
+ match: e.match,
157
+ suggestion: { role: n.role }
158
+ });
159
+ continue;
160
+ }
161
+ let s = n.filter((t) => t.role === e.match.role);
162
+ if (s.length > 0) {
163
+ let n = se(e.match.name, s.map((e) => e.name));
164
+ if (n) {
165
+ o.add(`${e.match.role}|${n}`), a.push({
166
+ severity: "warning",
167
+ kind: "renamed",
168
+ screenId: t.id,
169
+ message: `No element with role="${e.match.role}" name="${e.match.name}". Closest match: name="${n}".`,
170
+ match: e.match,
171
+ suggestion: { name: n }
172
+ });
173
+ continue;
174
+ }
175
+ }
176
+ a.push({
177
+ severity: "error",
178
+ kind: "removed",
179
+ screenId: t.id,
180
+ message: `No element with role="${e.match.role}" name="${e.match.name}".`,
181
+ match: e.match
182
+ });
183
+ }
184
+ let s = new Set(t.overlays.map((e) => `${e.match.role}|${e.match.name}`));
185
+ for (let e of n) {
186
+ if (!oe(e.role)) continue;
187
+ let n = `${e.role}|${e.name}`;
188
+ s.has(n) || o.has(n) || a.push({
189
+ severity: "warning",
190
+ kind: "added",
191
+ screenId: t.id,
192
+ message: `New ${e.role} "${e.name}" on the page has no <Overlay>.`,
193
+ suggestion: {
194
+ role: e.role,
195
+ name: e.name
196
+ }
197
+ });
198
+ }
199
+ return r !== void 0 && i !== void 0 && C(r) !== C(i) && a.push({
200
+ severity: "info",
201
+ kind: "attribute-drift",
202
+ screenId: t.id,
203
+ message: "`annot:attributes` block no longer matches live element attributes. Run `annot docs sync` to update."
204
+ }), a;
205
+ }
206
+ function ie(e) {
207
+ return b({
208
+ screen: e.screen,
209
+ liveSnapshot: g(e.liveSnapshotYaml),
210
+ storedAttributesYaml: e.storedAttributesYaml,
211
+ freshAttributesYaml: e.freshAttributesYaml
212
+ });
213
+ }
214
+ function x(e) {
215
+ let t = 0, n = 0, r = 0;
216
+ for (let i of e) i.severity === "error" ? t++ : i.severity === "warning" ? n++ : r++;
217
+ return {
218
+ errors: t,
219
+ warnings: n,
220
+ infos: r
221
+ };
222
+ }
223
+ var ae = new Set([
224
+ "button",
225
+ "checkbox",
226
+ "combobox",
227
+ "link",
228
+ "menuitem",
229
+ "menuitemcheckbox",
230
+ "menuitemradio",
231
+ "option",
232
+ "radio",
233
+ "searchbox",
234
+ "slider",
235
+ "spinbutton",
236
+ "switch",
237
+ "tab",
238
+ "textbox",
239
+ "treeitem"
240
+ ]);
241
+ function oe(e) {
242
+ return ae.has(e);
243
+ }
244
+ function se(e, t) {
245
+ if (t.length === 0) return null;
246
+ let n = t[0], r = S(e, n);
247
+ for (let i of t.slice(1)) {
248
+ let t = S(e, i);
249
+ t > r && (n = i, r = t);
250
+ }
251
+ return r > .3 ? n : null;
252
+ }
253
+ function S(e, t) {
254
+ if (!e || !t) return 0;
255
+ let n = (e) => {
256
+ let t = /* @__PURE__ */ new Set();
257
+ for (let n = 0; n < e.length - 2; n++) t.add(e.slice(n, n + 3));
258
+ return t;
259
+ }, r = n(e), i = n(t);
260
+ if (r.size === 0 || i.size === 0) return 0;
261
+ let a = 0;
262
+ for (let e of r) i.has(e) && a++;
263
+ return a / Math.max(r.size, i.size);
264
+ }
265
+ function C(e) {
266
+ return e.split(/\r?\n/).map((e) => e.trimEnd()).filter((e) => e.length > 0).join("\n");
267
+ }
268
+ function w(e) {
269
+ return e.overlays.length > 0;
270
+ }
271
+ function T(e) {
272
+ return e.filter(w);
273
+ }
274
+ //#endregion
275
+ //#region src/config.ts
276
+ var E = h.enum([
277
+ "cover",
278
+ "history",
279
+ "list",
280
+ "screen",
281
+ "reference"
282
+ ]), ce = h.object({
283
+ book: h.string().optional(),
284
+ sheet: h.string().optional(),
285
+ sheets: h.record(h.string(), h.string()).optional(),
286
+ role: E.optional(),
287
+ order: h.number().optional()
288
+ }).strict().refine((e) => !(e.sheet && e.sheets), "Specify either `xlsx.sheet` (single sheet) or `xlsx.sheets` (multi-sheet), not both."), le = h.record(h.string(), h.unknown()), D = h.object({
289
+ id: h.string().min(1, "`annot.id` is required."),
290
+ title: h.string().optional(),
291
+ purpose: h.string().optional(),
292
+ meta: le.optional(),
293
+ xlsx: ce.optional()
294
+ }).strict(), ue = h.object({
295
+ template: h.string().optional(),
296
+ templateSheets: h.object({
297
+ cover: h.string().optional(),
298
+ history: h.string().optional(),
299
+ list: h.string().optional(),
300
+ screen: h.string().optional(),
301
+ reference: h.string().optional()
302
+ }).strict().optional()
303
+ }).strict(), O = h.object({
304
+ meta: h.record(h.string(), h.unknown()).optional(),
305
+ xlsx: h.object({
306
+ defaultBook: h.string().optional(),
307
+ books: h.record(h.string(), ue).optional()
308
+ }).strict().optional()
309
+ }).strict();
310
+ function de(e) {
311
+ let t = O.safeParse(e);
312
+ if (!t.success) {
313
+ let e = t.error.issues.map((e) => ` - ${e.path.length ? e.path.join(".") + ": " : ""}${e.message}`).join("\n");
314
+ throw Error(`Invalid \`annot-docs.config.ts\`:\n${e}`);
315
+ }
316
+ return t.data;
317
+ }
318
+ function fe(e) {
319
+ return (e.xlsx?.role ?? "screen") === "screen";
320
+ }
321
+ //#endregion
322
+ //#region src/mdx.ts
323
+ var k = "annot:snapshot", pe = "/annot:snapshot", A = "annot:attributes", me = "/annot:attributes", he = /^\s*\/\*\s*([^\s*]+)\s*([\s\S]*?)\*\/\s*$/;
324
+ async function j(e) {
325
+ return M(await t(e, "utf8"), { filePath: e });
326
+ }
327
+ function M(e, t = {}) {
328
+ let n = p().use(f).use(u, ["yaml"]).use(d).parse(e), r = N(n, t.filePath);
329
+ if (!r) return null;
330
+ let i = [], a = [], o = [], s = [], c = {};
331
+ return m(n, (t) => {
332
+ switch (t.type) {
333
+ case "mdxJsxFlowElement":
334
+ case "mdxJsxTextElement":
335
+ F(t, e, {
336
+ screens: i,
337
+ transitions: a,
338
+ history: o,
339
+ screenLists: s
340
+ });
341
+ return;
342
+ case "mdxFlowExpression":
343
+ case "mdxTextExpression":
344
+ _e(t, c);
345
+ return;
346
+ }
347
+ }), {
348
+ frontmatter: r,
349
+ screens: i,
350
+ transitions: a,
351
+ history: o,
352
+ screenLists: s,
353
+ commentBlocks: c,
354
+ source: e
355
+ };
356
+ }
357
+ function N(e, t) {
358
+ let n = e.children.find((e) => e.type === "yaml");
359
+ if (!n || !n.value) return null;
360
+ let r = l.load(n.value);
361
+ if (!r || typeof r != "object") return null;
362
+ let i = r.annot;
363
+ if (!i || typeof i != "object") return null;
364
+ let a = D.safeParse(i);
365
+ if (!a.success) {
366
+ let e = t ? ` in ${t}` : "";
367
+ throw Error(`Invalid \`annot:\` frontmatter${e}:\n${P(a.error)}`);
368
+ }
369
+ return a.data;
370
+ }
371
+ function P(e) {
372
+ return e.issues.map((e) => ` - ${e.path.length ? e.path.join(".") + ": " : ""}${e.message}`).join("\n");
373
+ }
374
+ function F(e, t, n) {
375
+ let r = e;
376
+ switch (r.name) {
377
+ case "Screen":
378
+ n.screens.push(I(r, t));
379
+ return;
380
+ case "Transition":
381
+ n.transitions.push(R(r, t));
382
+ return;
383
+ case "HistoryEntry":
384
+ n.history.push(ge(r, t));
385
+ return;
386
+ case "ScreenList":
387
+ n.screenLists.push(z(r));
388
+ return;
389
+ }
390
+ }
391
+ function I(e, t) {
392
+ let n = B(e), r = [], i = (n) => {
393
+ let a = n;
394
+ if (a.name === "Overlay") {
395
+ r.push(L(a, t));
396
+ return;
397
+ }
398
+ if (!(a.name === "Screen" && a !== e)) for (let e of a.children ?? []) i(e);
399
+ };
400
+ for (let t of e.children ?? []) i(t);
401
+ let a = V(n.id);
402
+ if (!a) throw Error("<Screen> requires an `id` prop.");
403
+ return {
404
+ id: a,
405
+ src: V(n.src),
406
+ overlays: r
407
+ };
408
+ }
409
+ function L(e, t) {
410
+ let n = B(e), r = n.match;
411
+ if (!H(r)) throw Error("<Overlay> requires a `match` prop with at least `{ role, name }`.");
412
+ return {
413
+ match: r,
414
+ intent: V(n.intent),
415
+ number: ye(n.number),
416
+ body: U(e, t)
417
+ };
418
+ }
419
+ function R(e, t) {
420
+ let n = B(e), r = n.trigger;
421
+ if (!H(r)) throw Error("<Transition> requires a `trigger` prop with at least `{ role, name }`.");
422
+ return {
423
+ trigger: r,
424
+ on: V(n.on),
425
+ to: V(n.to),
426
+ body: U(e, t)
427
+ };
428
+ }
429
+ function ge(e, t) {
430
+ let n = B(e), r = V(n.version) ?? "", i = V(n.date) ?? "", a = V(n.author) ?? "";
431
+ if (!r || !i) throw Error("<HistoryEntry> requires `version` and `date` props.");
432
+ return {
433
+ version: r,
434
+ date: i,
435
+ author: a,
436
+ body: U(e, t)
437
+ };
438
+ }
439
+ function z(e) {
440
+ let t = B(e);
441
+ return {
442
+ book: V(t.book),
443
+ sort: V(t.sort)
444
+ };
445
+ }
446
+ function _e(e, t) {
447
+ let n = e.value;
448
+ if (typeof n != "string") return;
449
+ let r = n.match(he);
450
+ if (!r) return;
451
+ let i = (r[1] ?? "").trim(), a = (r[2] ?? "").trim();
452
+ switch (i) {
453
+ case k:
454
+ t.snapshot = a;
455
+ return;
456
+ case A:
457
+ t.attributes = a;
458
+ return;
459
+ case pe:
460
+ case me: return;
461
+ }
462
+ }
463
+ function B(e) {
464
+ let t = {};
465
+ for (let n of e.attributes ?? []) {
466
+ let e = n;
467
+ if (e.type !== "mdxJsxAttribute" || !e.name) continue;
468
+ if (e.value === null || e.value === void 0) {
469
+ t[e.name] = !0;
470
+ continue;
471
+ }
472
+ if (typeof e.value == "string") {
473
+ t[e.name] = e.value;
474
+ continue;
475
+ }
476
+ let r = e.value;
477
+ r.type === "mdxJsxAttributeValueExpression" && typeof r.value == "string" && (t[e.name] = ve(r.value));
478
+ }
479
+ return t;
480
+ }
481
+ function ve(e) {
482
+ let t = e.trim();
483
+ if (t) {
484
+ if (t === "true") return !0;
485
+ if (t === "false") return !1;
486
+ if (t === "null") return null;
487
+ if (/^-?\d+(\.\d+)?$/.test(t)) return Number.parseFloat(t);
488
+ if (t.startsWith("\"") && t.endsWith("\"")) try {
489
+ return JSON.parse(t);
490
+ } catch {
491
+ return t.slice(1, -1);
492
+ }
493
+ if (t.startsWith("'") && t.endsWith("'")) {
494
+ let e = t.slice(1, -1), n = e.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
495
+ try {
496
+ return JSON.parse(`"${n}"`);
497
+ } catch {
498
+ return e;
499
+ }
500
+ }
501
+ if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
502
+ let e = t.replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)(\s*:)/g, "$1\"$2\"$3").replace(/'([^'\\]*)'/g, "\"$1\"").replace(/,(\s*[}\]])/g, "$1");
503
+ try {
504
+ return JSON.parse(e);
505
+ } catch {
506
+ return t;
507
+ }
508
+ }
509
+ return t;
510
+ }
511
+ }
512
+ function V(e) {
513
+ return typeof e == "string" ? e : void 0;
514
+ }
515
+ function ye(e) {
516
+ return typeof e == "number" ? e : void 0;
517
+ }
518
+ function H(e) {
519
+ if (!e || typeof e != "object") return !1;
520
+ let t = e;
521
+ return !(typeof t.role != "string" || typeof t.name != "string" || t.under !== void 0 && !H(t.under));
522
+ }
523
+ function U(e, t) {
524
+ let n = e.position;
525
+ if (!n) return "";
526
+ let r = t.slice(n.start.offset, n.end.offset);
527
+ if (/\/>\s*$/.test(r)) return "";
528
+ let i = r.indexOf(">"), a = r.lastIndexOf("</");
529
+ return i < 0 || a < 0 || a <= i ? "" : r.slice(i + 1, a).trim();
530
+ }
531
+ function W(e, t) {
532
+ let n = e;
533
+ return t.snapshot !== void 0 && (n = G(n, k, t.snapshot)), t.attributes !== void 0 && (n = G(n, A, t.attributes)), n;
534
+ }
535
+ function G(e, t, n) {
536
+ let r = new RegExp(String.raw`\{\s*/\*\s*` + xe(t) + String.raw`\s*[\s\S]*?\*/\s*\}`, "m"), i = be(t, n);
537
+ return r.test(e) ? e.replace(r, i) : `${e.trimEnd()}\n\n${i}\n`;
538
+ }
539
+ function be(e, t) {
540
+ let n = t.trim();
541
+ return n ? `{/* ${e}\n${n}\n*/}` : `{/* ${e} */}`;
542
+ }
543
+ function xe(e) {
544
+ return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
545
+ }
546
+ //#endregion
547
+ //#region src/fixture.ts
548
+ var K = [
549
+ "type",
550
+ "required",
551
+ "placeholder",
552
+ "maxlength",
553
+ "minlength",
554
+ "pattern",
555
+ "min",
556
+ "max",
557
+ "step",
558
+ "disabled",
559
+ "readonly",
560
+ "checked",
561
+ "aria-required",
562
+ "aria-disabled",
563
+ "aria-readonly"
564
+ ], Se = c.extend({ screen: async ({ page: e }, t) => {
565
+ await t({ capture: (t) => q(e, t) });
566
+ } });
567
+ async function q(e, t) {
568
+ let n = await j(t.mdxPath);
569
+ if (!n) throw Error(`captureScreen: ${t.mdxPath} has no \`annot:\` frontmatter — refusing to write.`);
570
+ let i = n.screens.find((e) => e.id === t.id);
571
+ if (!i) throw Error(`captureScreen: ${t.mdxPath} has no <Screen id="${t.id}"> block.`);
572
+ let a = await (t.rootLocator ?? e.locator("body")).ariaSnapshot({
573
+ mode: "ai",
574
+ boxes: !0
575
+ }), o = await J(e, i.overlays, t.attributeWhitelist ?? K), s = W(n.source, {
576
+ snapshot: a.trim(),
577
+ attributes: o
578
+ });
579
+ await r(t.mdxPath, s, "utf8");
580
+ }
581
+ async function J(e, t, n) {
582
+ let r = [];
583
+ for (let i of t) {
584
+ let t = e.getByRole(i.match.role, {
585
+ name: i.match.name,
586
+ exact: !0
587
+ });
588
+ if (await t.count() !== 1) continue;
589
+ let a = await t.evaluate((e, t) => {
590
+ let n = {};
591
+ for (let r of t) {
592
+ let t = e.getAttribute(r);
593
+ t !== null && (n[r] = t);
594
+ }
595
+ return n;
596
+ }, n), o = Object.keys(a);
597
+ if (o.length !== 0) {
598
+ r.push(`${i.match.role} "${i.match.name}":`);
599
+ for (let e of o) r.push(` ${e}: ${a[e]}`);
600
+ }
601
+ }
602
+ return r.join("\n");
603
+ }
604
+ //#endregion
605
+ //#region src/cli.ts
606
+ async function Ce(e, t = {}) {
607
+ let n = t.stdout ?? ((e) => process.stdout.write(`${e}\n`)), r = t.stderr ?? ((e) => process.stderr.write(`${e}\n`)), i = t.cwd ?? process.cwd(), [, , a, ...o] = e;
608
+ if (a !== "init" && a !== "sync" && a !== "lint") return r(we), +!!a;
609
+ try {
610
+ switch (a) {
611
+ case "init": return await Te(o, {
612
+ cwd: i,
613
+ stdout: n,
614
+ stderr: r
615
+ });
616
+ case "sync": return await ke(o, {
617
+ ...t,
618
+ cwd: i,
619
+ stdout: n,
620
+ stderr: r
621
+ });
622
+ case "lint": return await Ae(o, {
623
+ ...t,
624
+ cwd: i,
625
+ stdout: n,
626
+ stderr: r
627
+ });
628
+ }
629
+ } catch (e) {
630
+ return r(`annot docs ${a}: ${e.message}`), 1;
631
+ }
632
+ }
633
+ var we = [
634
+ "annot docs <command> [options]",
635
+ "",
636
+ "Commands:",
637
+ " init Scaffold annot-docs.config.ts + sample files",
638
+ " sync --url <baseUrl> Re-capture snapshot + attrs into every annot MDX",
639
+ " lint --url <baseUrl> Report drift between annot MDXs and the live page",
640
+ "",
641
+ "Options:",
642
+ " --url <baseUrl> Base URL Playwright navigates to (sync / lint)",
643
+ " --root <dir> Override MDX search root (default: docs/)",
644
+ " --help Show this help"
645
+ ].join("\n");
646
+ async function Te(t, n) {
647
+ let { cwd: a, stdout: o } = n, c = [
648
+ {
649
+ path: "annot-docs.config.ts",
650
+ content: Ee
651
+ },
652
+ {
653
+ path: "tests/docs/example.spec.ts",
654
+ content: De
655
+ },
656
+ {
657
+ path: "docs/books/example/SC-001-login.mdx",
658
+ content: Oe
659
+ }
660
+ ], l = 0;
661
+ for (let t of c) {
662
+ let n = s(a, t.path);
663
+ try {
664
+ await e(i(n), { recursive: !0 }), await r(n, t.content, { flag: "wx" }), o(` created ${t.path}`), l++;
665
+ } catch (e) {
666
+ if (e.code === "EEXIST") o(` exists ${t.path} (skipped)`);
667
+ else throw e;
668
+ }
669
+ }
670
+ return o(l === 0 ? "annot docs init: nothing to do (all targets exist)" : `annot docs init: wrote ${l} file(s)`), 0;
671
+ }
672
+ var Ee = "// Living product docs config for annot.\n// See https://github.com/ingcreators/annot/blob/main/docs/plans/living-product-docs.md\nimport { defineConfig } from \"@ingcreators/annot-product-docs\";\n\nexport default defineConfig({\n meta: {\n projectName: \"Example\",\n },\n xlsx: {\n defaultBook: \"Screen spec\",\n books: {\n \"Screen spec\": {\n // Drop your customer-supplied template here (Phase 3) and\n // the Excel adapter will fill it. Until then the OSS\n // default layout applies.\n // template: \"./templates/customer-screen-spec.xlsx\",\n },\n },\n },\n});\n", De = "// Tour file — runs through every screen and refreshes the\n// matching MDX's annot:snapshot / annot:attributes blocks.\n//\n// Run with: pnpm playwright test tests/docs/\n\nimport { test } from \"@ingcreators/annot-product-docs\";\n\ntest.describe.configure({ mode: \"serial\" });\n\ntest(\"login flow\", async ({ page, screen }) => {\n await page.goto(\"/login\");\n await screen.capture({\n id: \"login\",\n mdxPath: \"docs/books/example/SC-001-login.mdx\",\n });\n});\n", Oe = "---\nannot:\n id: SC-001\n title: Login screen\n meta:\n author: TODO\n xlsx:\n book: Screen spec\n sheet: SC-001 Login\n role: screen\n order: 100\n---\n\nimport { Screen, Overlay } from \"@ingcreators/annot-product-docs-astro\";\n\n# Login screen\n\nEnter your credentials to access the system.\n\n<Screen id=\"login\" src=\"./shots/login.png\">\n\n<Overlay match={{ role: \"textbox\", name: \"Email\" }} intent=\"required\" number={1}>\n**Email** — Enter your registered email address.\n</Overlay>\n\n<Overlay match={{ role: \"button\", name: \"Sign in\" }} intent=\"action\" number={2}>\nClick to sign in.\n</Overlay>\n\n</Screen>\n";
673
+ async function ke(e, t) {
674
+ let { cwd: n, stdout: r, stderr: i } = t, a = Z(e);
675
+ if (!a.url) return i("annot docs sync: --url <baseUrl> is required."), 1;
676
+ let c = await $(await Q(s(n, a.root ?? "docs")));
677
+ if (c.length === 0) return r(`annot docs sync: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
678
+ r(`annot docs sync: ${c.length} MDX file(s) → ${a.url}`);
679
+ let { newPage: l, close: u } = await Y(a.url, t);
680
+ try {
681
+ for (let e of c) {
682
+ let t = await j(e);
683
+ if (!t) continue;
684
+ let i = T(t.screens);
685
+ if (i.length === 0) {
686
+ r(` skip ${o(n, e)} (no <Screen> blocks)`);
687
+ continue;
688
+ }
689
+ for (let t of i) {
690
+ let { page: i, dispose: a } = await l(t.src ?? "/");
691
+ try {
692
+ await q(i, {
693
+ id: t.id,
694
+ mdxPath: e
695
+ }), r(` synced ${o(n, e)} (screen=${t.id})`);
696
+ } finally {
697
+ await a();
698
+ }
699
+ }
700
+ }
701
+ } finally {
702
+ await u();
703
+ }
704
+ return 0;
705
+ }
706
+ async function Ae(e, t) {
707
+ let { cwd: n, stdout: r, stderr: i } = t, a = Z(e);
708
+ if (!a.url) return i("annot docs lint: --url <baseUrl> is required."), 1;
709
+ let c = await $(await Q(s(n, a.root ?? "docs")));
710
+ if (c.length === 0) return a.json ? r(JSON.stringify({
711
+ findings: [],
712
+ summary: {
713
+ errors: 0,
714
+ warnings: 0,
715
+ infos: 0
716
+ }
717
+ })) : r(`annot docs lint: no MDX files with \`annot:\` frontmatter under ${a.root ?? "docs"}.`), 0;
718
+ a.json || r(`annot docs lint: ${c.length} MDX file(s) → ${a.url}`);
719
+ let l = [], u = /* @__PURE__ */ new Set(), { newPage: d, close: f } = await Y(a.url, t);
720
+ try {
721
+ for (let e of c) {
722
+ let t = await j(e);
723
+ if (t) for (let r of T(t.screens)) {
724
+ let { page: t, dispose: i } = await d(r.src ?? "/");
725
+ try {
726
+ let i = b({
727
+ screen: r,
728
+ liveSnapshot: g(await t.locator("body").ariaSnapshot({ mode: "ai" }))
729
+ });
730
+ for (let t of i) l.push({
731
+ file: o(n, e),
732
+ finding: t
733
+ }), u.add(e);
734
+ } finally {
735
+ await i();
736
+ }
737
+ }
738
+ }
739
+ } finally {
740
+ await f();
741
+ }
742
+ if (a.json) {
743
+ let e = x(l.map((e) => e.finding));
744
+ r(JSON.stringify({
745
+ findings: l.map(({ file: e, finding: t }) => ({
746
+ file: e,
747
+ severity: t.severity,
748
+ kind: t.kind,
749
+ screenId: t.screenId,
750
+ message: t.message,
751
+ match: t.match,
752
+ suggestion: t.suggestion
753
+ })),
754
+ summary: e
755
+ }));
756
+ } else {
757
+ for (let { file: e, finding: t } of l) r(`${je(t.severity)} ${e} [${t.screenId}] ${t.kind}: ${t.message}`);
758
+ let e = x(l.map((e) => e.finding));
759
+ r(`annot docs lint: ${e.errors} error(s), ${e.warnings} warning(s), ${e.infos} info(s).`);
760
+ }
761
+ let p = 0;
762
+ if (a.fix && u.size > 0) {
763
+ let { newPage: e, close: n } = await Y(a.url, t);
764
+ try {
765
+ for (let t of u) {
766
+ let n = await j(t);
767
+ if (n) for (let r of T(n.screens)) {
768
+ let { page: n, dispose: i } = await e(r.src ?? "/");
769
+ try {
770
+ await q(n, {
771
+ id: r.id,
772
+ mdxPath: t
773
+ }), p++;
774
+ } finally {
775
+ await i();
776
+ }
777
+ }
778
+ }
779
+ } finally {
780
+ await n();
781
+ }
782
+ a.json || r(`annot docs lint --fix: refreshed snapshot/attributes for ${p} screen(s).`);
783
+ }
784
+ let m = x(l.map((e) => e.finding));
785
+ return a.ci ? +(m.errors + m.warnings > 0) : +(m.errors > 0);
786
+ }
787
+ async function Y(e, t) {
788
+ if (t.newPage) return {
789
+ async newPage(n) {
790
+ let { page: r, close: i } = await t.newPage(X(e, n));
791
+ return {
792
+ page: r,
793
+ dispose: i
794
+ };
795
+ },
796
+ async close() {}
797
+ };
798
+ let { chromium: n } = await import("playwright-core"), r = await n.launch({ headless: !0 });
799
+ return {
800
+ async newPage(t) {
801
+ let n = await r.newContext(), i = await n.newPage();
802
+ return await i.goto(X(e, t)), {
803
+ page: i,
804
+ dispose: async () => {
805
+ await n.close();
806
+ }
807
+ };
808
+ },
809
+ async close() {
810
+ await r.close();
811
+ }
812
+ };
813
+ }
814
+ function X(e, t) {
815
+ return /^https?:\/\//.test(t) ? t : t.startsWith("/") ? e.replace(/\/$/, "") + t : e;
816
+ }
817
+ function Z(e) {
818
+ let t = {};
819
+ for (let n = 0; n < e.length; n++) {
820
+ let r = e[n];
821
+ r === "--url" || r === "-u" ? t.url = e[++n] : r === "--root" || r === "-r" ? t.root = e[++n] : r === "--json" ? t.json = !0 : r === "--ci" ? t.ci = !0 : r === "--fix" && (t.fix = !0);
822
+ }
823
+ return t;
824
+ }
825
+ async function Q(e) {
826
+ let t = [], r = [e];
827
+ for (; r.length > 0;) {
828
+ let e = r.pop(), i;
829
+ try {
830
+ i = await n(e, { withFileTypes: !0 });
831
+ } catch (e) {
832
+ if (e.code === "ENOENT") continue;
833
+ throw e;
834
+ }
835
+ for (let n of i) {
836
+ let i = a(e, n.name);
837
+ if (n.isDirectory()) {
838
+ if (n.name === "node_modules" || n.name.startsWith(".")) continue;
839
+ r.push(i);
840
+ } else n.isFile() && n.name.endsWith(".mdx") && t.push(i);
841
+ }
842
+ }
843
+ return t.sort();
844
+ }
845
+ async function $(e) {
846
+ let t = [];
847
+ for (let n of e) try {
848
+ await j(n) && t.push(n);
849
+ } catch {}
850
+ return t;
851
+ }
852
+ function je(e) {
853
+ switch (e) {
854
+ case "error": return "ERROR ";
855
+ case "warning": return "WARN ";
856
+ case "info": return "INFO ";
857
+ }
858
+ }
859
+ //#endregion
860
+ export { K as DEFAULT_ATTR_WHITELIST, O as annotDocsConfigSchema, D as annotFrontmatterSchema, q as captureScreen, J as collectAttributesYaml, de as defineConfig, b as detectDrift, ie as detectDriftFromYaml, $ as filterAnnotMdxFiles, w as isLintableScreen, fe as isScreenRole, T as lintableScreens, Ce as main, M as parseMdx, j as parseMdxFile, g as parseSnapshot, _ as resolveMatch, ee as resolveOverlays, x as summariseDrift, Se as test, W as updateCommentBlocks, Q as walkMdx };