@ozzylabs/feedradar 0.1.1 → 0.1.2

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.
Files changed (46) hide show
  1. package/README.ja.md +13 -5
  2. package/README.md +13 -5
  3. package/dist/cli/doctor.d.ts +83 -0
  4. package/dist/cli/doctor.d.ts.map +1 -0
  5. package/dist/cli/doctor.js +260 -0
  6. package/dist/cli/doctor.js.map +1 -0
  7. package/dist/cli/index.d.ts.map +1 -1
  8. package/dist/cli/index.js +2 -2
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/source.d.ts.map +1 -1
  11. package/dist/cli/source.js +6 -3
  12. package/dist/cli/source.js.map +1 -1
  13. package/dist/cli/watch.d.ts +16 -0
  14. package/dist/cli/watch.d.ts.map +1 -1
  15. package/dist/cli/watch.js +3 -0
  16. package/dist/cli/watch.js.map +1 -1
  17. package/dist/core/feeds/_html-common.d.ts +30 -0
  18. package/dist/core/feeds/_html-common.d.ts.map +1 -0
  19. package/dist/core/feeds/_html-common.js +192 -0
  20. package/dist/core/feeds/_html-common.js.map +1 -0
  21. package/dist/core/feeds/html-js.d.ts +50 -0
  22. package/dist/core/feeds/html-js.d.ts.map +1 -0
  23. package/dist/core/feeds/html-js.js +135 -0
  24. package/dist/core/feeds/html-js.js.map +1 -0
  25. package/dist/core/feeds/html.d.ts +1 -7
  26. package/dist/core/feeds/html.d.ts.map +1 -1
  27. package/dist/core/feeds/html.js +5 -180
  28. package/dist/core/feeds/html.js.map +1 -1
  29. package/dist/core/feeds/index.d.ts.map +1 -1
  30. package/dist/core/feeds/index.js +2 -0
  31. package/dist/core/feeds/index.js.map +1 -1
  32. package/dist/core/playwright-check.d.ts +134 -0
  33. package/dist/core/playwright-check.d.ts.map +1 -0
  34. package/dist/core/playwright-check.js +98 -0
  35. package/dist/core/playwright-check.js.map +1 -0
  36. package/dist/core/watcher.d.ts +17 -0
  37. package/dist/core/watcher.d.ts.map +1 -1
  38. package/dist/core/watcher.js +59 -0
  39. package/dist/core/watcher.js.map +1 -1
  40. package/dist/schemas/source.d.ts +42 -0
  41. package/dist/schemas/source.d.ts.map +1 -1
  42. package/dist/schemas/source.js +42 -7
  43. package/dist/schemas/source.js.map +1 -1
  44. package/dist/templates/agents/AGENTS.md +2 -2
  45. package/dist/templates/feedradar.md +2 -2
  46. package/package.json +11 -1
@@ -1,184 +1,9 @@
1
- import { createHash } from "node:crypto";
2
- import { parse as parseHtml } from "node-html-parser";
3
- import { ItemSchema } from "../../schemas/index.js";
4
- import { deriveItemId, deriveStableKey } from "./derive-id.js";
1
+ import { CONTENT_HASH_PREFIX, contentHash, parseHtmlDocument } from "./_html-common.js";
2
+ // Re-export shared primitives so existing imports (and tests) that pulled
3
+ // `parseHtmlDocument` from this module keep working after the
4
+ // `_html-common.ts` split (ADR-0010 §D1, no behavior change).
5
+ export { parseHtmlDocument } from "./_html-common.js";
5
6
  const USER_AGENT = "feedradar/0.0.0 (+https://github.com/ozzy-labs/feedradar)";
6
- /**
7
- * Prefix that flags an `lastEtag` slot as carrying a content hash rather than
8
- * an actual HTTP ETag. We reuse the `lastEtag` field so this Phase does not
9
- * have to migrate `SourceState` (see `docs/design/source-html.md`).
10
- */
11
- const CONTENT_HASH_PREFIX = "sha256:";
12
- /** Attributes the adapter checks before falling back to text content. */
13
- const DATETIME_ATTRS = ["datetime", "content", "value"];
14
- /**
15
- * Convert an `HTMLElement | null` to its trimmed text, or `undefined` when
16
- * the selector did not match. We always trim because raw scrapes routinely
17
- * carry surrounding whitespace from formatted markup.
18
- */
19
- function textOf(el) {
20
- if (!el)
21
- return undefined;
22
- const text = el.text?.trim();
23
- return text ? text : undefined;
24
- }
25
- /**
26
- * Apply a CSS selector relative to `root` and return the first match.
27
- * `node-html-parser` returns `null` instead of throwing for invalid input,
28
- * which matches what callers want here (a missing field, not a hard error).
29
- */
30
- function queryFirst(root, selector) {
31
- return root.querySelector(selector);
32
- }
33
- /**
34
- * Resolve the `link` selector to an `href` (or text fallback).
35
- *
36
- * Anchor tags expose the URL via `href` so we prefer the attribute. When the
37
- * selector points at a non-anchor (e.g. a `<div data-link>` wrapper used by
38
- * some changelog layouts), we fall back to text content so the adapter can
39
- * still operate, deferring URL validation to `ItemSchema`.
40
- */
41
- function pickLink(el) {
42
- if (!el)
43
- return undefined;
44
- const href = el.getAttribute("href");
45
- if (href && href.trim())
46
- return href.trim();
47
- return textOf(el);
48
- }
49
- /**
50
- * Resolve `publishedAt` to a candidate string for `new Date()`.
51
- *
52
- * `<time datetime="2026-05-12">` and `<meta content="..."/>` markup hide the
53
- * canonical timestamp in attributes; the visible text is often a
54
- * localized "May 12, 2026" that is harder to parse reliably. We probe the
55
- * known attributes first, then fall back to element text.
56
- */
57
- function pickDatetime(el) {
58
- if (!el)
59
- return undefined;
60
- for (const attr of DATETIME_ATTRS) {
61
- const value = el.getAttribute(attr);
62
- if (value && value.trim())
63
- return value.trim();
64
- }
65
- return textOf(el);
66
- }
67
- /**
68
- * Try to parse a candidate timestamp into ISO 8601. Returns `undefined` for
69
- * unparseable inputs so the item can still be emitted (RSS adapter parity).
70
- */
71
- function toIsoDate(value) {
72
- if (!value)
73
- return undefined;
74
- const date = new Date(value);
75
- if (Number.isNaN(date.getTime()))
76
- return undefined;
77
- return date.toISOString();
78
- }
79
- /** Collect the trimmed text of every match for `selector`. */
80
- function collectTags(root, selector) {
81
- if (!selector)
82
- return undefined;
83
- const tags = root
84
- .querySelectorAll(selector)
85
- .map((el) => el.text?.trim())
86
- .filter((t) => !!t && t.length > 0);
87
- return tags.length > 0 ? tags : undefined;
88
- }
89
- /**
90
- * Resolve a relative `link` against the source URL.
91
- *
92
- * Many sites publish `<a href="/changelog/foo">` rather than absolute URLs;
93
- * without resolution `ItemSchema`'s `z.string().url()` would drop them. We
94
- * intentionally swallow `URL` constructor errors so a malformed `link`
95
- * surfaces as a normal validation drop later instead of breaking the whole
96
- * fetch.
97
- */
98
- function resolveUrl(raw, base) {
99
- try {
100
- return new URL(raw, base).toString();
101
- }
102
- catch {
103
- return raw;
104
- }
105
- }
106
- /** Normalize one matched element into an Item, or `null` to drop it. */
107
- function parseItem(itemEl, selectors, source, fetchedAt) {
108
- const title = textOf(queryFirst(itemEl, selectors.title));
109
- const linkRaw = pickLink(queryFirst(itemEl, selectors.link));
110
- if (!title || !linkRaw)
111
- return null;
112
- const url = resolveUrl(linkRaw, source.url);
113
- const summary = selectors.summary ? textOf(queryFirst(itemEl, selectors.summary)) : undefined;
114
- const body = selectors.body ? textOf(queryFirst(itemEl, selectors.body)) : undefined;
115
- const publishedAt = selectors.publishedAt
116
- ? toIsoDate(pickDatetime(queryFirst(itemEl, selectors.publishedAt)))
117
- : undefined;
118
- const tags = collectTags(itemEl, selectors.tags);
119
- const stableKey = deriveStableKey({
120
- url,
121
- fallbackHashInputs: [title, publishedAt],
122
- });
123
- const id = deriveItemId(title, stableKey);
124
- // Preserve a structured snapshot of the raw scrape rather than the
125
- // `HTMLElement` instance itself — the watcher serializes `raw` to YAML and
126
- // we want the on-disk payload to be diff-friendly.
127
- const raw = { title, link: linkRaw };
128
- if (summary !== undefined)
129
- raw.summary = summary;
130
- if (body !== undefined)
131
- raw.body = body;
132
- if (publishedAt !== undefined)
133
- raw.publishedAt = publishedAt;
134
- if (tags !== undefined)
135
- raw.tags = tags;
136
- return validateItem({
137
- id,
138
- sourceId: source.id,
139
- title,
140
- url,
141
- summary,
142
- publishedAt,
143
- fetchedAt,
144
- raw,
145
- });
146
- }
147
- function validateItem(candidate) {
148
- const result = ItemSchema.safeParse(candidate);
149
- // Items that fail validation (e.g. unresolvable URL) are dropped silently —
150
- // see rss.ts for the same fail-soft rationale.
151
- return result.success ? result.data : null;
152
- }
153
- /**
154
- * Parse an HTML document into validated `Item[]` using the source's
155
- * `selectors`. Exported so tests can drive the parser directly without
156
- * needing a fake HTTP layer.
157
- */
158
- export function parseHtmlDocument(html, source, fetchedAt) {
159
- if (!source.selectors) {
160
- throw new Error(`html adapter: source '${source.id}' has no selectors`);
161
- }
162
- const selectors = source.selectors;
163
- let root;
164
- try {
165
- root = parseHtml(html);
166
- }
167
- catch (e) {
168
- throw new Error(`html adapter: failed to parse HTML: ${e instanceof Error ? e.message : String(e)}`);
169
- }
170
- const itemEls = root.querySelectorAll(selectors.item);
171
- return itemEls
172
- .map((el) => parseItem(el, selectors, source, fetchedAt))
173
- .filter((i) => i !== null);
174
- }
175
- /**
176
- * Compute the sha256 of the raw response body, prefixed so callers can tell
177
- * it apart from a real ETag inside `SourceState.lastEtag`.
178
- */
179
- function contentHash(body) {
180
- return `${CONTENT_HASH_PREFIX}${createHash("sha256").update(body).digest("hex")}`;
181
- }
182
7
  /**
183
8
  * Issue an HTTP GET with conditional headers. The previous `lastEtag` slot
184
9
  * may contain either an actual ETag (mirror RSS behavior) or a `sha256:`
@@ -1 +1 @@
1
- {"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/core/feeds/html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAoB,KAAK,IAAI,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG/D,MAAM,UAAU,GAAG,2DAA2D,CAAC;AAE/E;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,SAAS,CAAC;AAEtC,yEAAyE;AACzE,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAU,CAAC;AAEjE;;;;GAIG;AACH,SAAS,MAAM,CAAC,EAAsB;IACpC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAiB,EAAE,QAAgB;IACrD,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,QAAQ,CAAC,EAAsB;IACtC,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,EAAsB;IAC1C,IAAI,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE;YAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,KAAyB;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACnD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,8DAA8D;AAC9D,SAAS,WAAW,CAAC,IAAiB,EAAE,QAA4B;IAClE,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI;SACd,gBAAgB,CAAC,QAAQ,CAAC;SAC1B,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,GAAW,EAAE,IAAY;IAC3C,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,SAAS,SAAS,CAChB,MAAmB,EACnB,SAA0B,EAC1B,MAAc,EACd,SAAiB;IAEjB,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAE5C,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9F,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW;QACvC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;QACpE,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IAEjD,MAAM,SAAS,GAAG,eAAe,CAAC;QAChC,GAAG;QACH,kBAAkB,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC;KACzC,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,YAAY,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAE1C,mEAAmE;IACnE,2EAA2E;IAC3E,mDAAmD;IACnD,MAAM,GAAG,GAA4B,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC9D,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;IACjD,IAAI,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IACxC,IAAI,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC;IAC7D,IAAI,IAAI,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;IAExC,OAAO,YAAY,CAAC;QAClB,EAAE;QACF,QAAQ,EAAE,MAAM,CAAC,EAAE;QACnB,KAAK;QACL,GAAG;QACH,OAAO;QACP,WAAW;QACX,SAAS;QACT,GAAG;KACJ,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,SAAkC;IACtD,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/C,4EAA4E;IAC5E,+CAA+C;IAC/C,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAc,EAAE,SAAiB;IAC/E,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,IAAiB,CAAC;IACtB,IAAI,CAAC;QACH,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,uCAAuC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACpF,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;SACxD,MAAM,CAAC,CAAC,CAAC,EAAa,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,GAAG,mBAAmB,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACpF,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,SAAS,CACtB,GAAW,EACX,SAAoB,EACpB,UAAmD,EAAE;IAMrD,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,mDAAmD;QAC3D,YAAY,EAAE,UAAU;KACzB,CAAC;IACF,0EAA0E;IAC1E,2EAA2E;IAC3E,WAAW;IACX,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAClE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,KAAK,EAAE,MAAc,EAAE,UAA8B,EAAE,EAAE,EAAE;QAChE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAK,UAAU,CAAC,KAA8B,CAAC;QAC9E,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC;QAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE;YACtD,IAAI,EAAE,QAAQ,EAAE,QAAQ;SACzB,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO;gBACL,KAAK,EAAE,EAAE;gBACT,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE;oBACL,aAAa,EAAE,SAAS;oBACxB,iEAAiE;oBACjE,oEAAoE;oBACpE,QAAQ,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE,QAAQ;iBAC9C;aACF,CAAC;QACJ,CAAC;QAED,yEAAyE;QACzE,uEAAuE;QACvE,wDAAwD;QACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,QAAQ,EAAE,QAAQ,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,cAAc,KAAK,QAAQ,EAAE,CAAC;YAClD,OAAO;gBACL,KAAK,EAAE,EAAE;gBACT,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE;oBACL,aAAa,EAAE,SAAS;oBACxB,QAAQ,EAAE,QAAQ;iBACnB;aACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAClE,OAAO;YACL,KAAK;YACL,KAAK,EAAE;gBACL,aAAa,EAAE,SAAS;gBACxB,qEAAqE;gBACrE,wDAAwD;gBACxD,QAAQ,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ;aACpC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/core/feeds/html.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGxF,0EAA0E;AAC1E,8DAA8D;AAC9D,8DAA8D;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,UAAU,GAAG,2DAA2D,CAAC;AAE/E;;;;GAIG;AACH,KAAK,UAAU,SAAS,CACtB,GAAW,EACX,SAAoB,EACpB,UAAmD,EAAE;IAMrD,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,mDAAmD;QAC3D,YAAY,EAAE,UAAU;KACzB,CAAC;IACF,0EAA0E;IAC1E,2EAA2E;IAC3E,WAAW;IACX,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAClE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAgB;IACtC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,KAAK,EAAE,MAAc,EAAE,UAA8B,EAAE,EAAE,EAAE;QAChE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAK,UAAU,CAAC,KAA8B,CAAC;QAC9E,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC;QAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE;YACtD,IAAI,EAAE,QAAQ,EAAE,QAAQ;SACzB,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO;gBACL,KAAK,EAAE,EAAE;gBACT,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE;oBACL,aAAa,EAAE,SAAS;oBACxB,iEAAiE;oBACjE,oEAAoE;oBACpE,QAAQ,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE,QAAQ;iBAC9C;aACF,CAAC;QACJ,CAAC;QAED,yEAAyE;QACzE,uEAAuE;QACvE,wDAAwD;QACxD,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,cAAc,GAAG,QAAQ,EAAE,QAAQ,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,cAAc,KAAK,QAAQ,EAAE,CAAC;YAClD,OAAO;gBACL,KAAK,EAAE,EAAE;gBACT,WAAW,EAAE,IAAI;gBACjB,KAAK,EAAE;oBACL,aAAa,EAAE,SAAS;oBACxB,QAAQ,EAAE,QAAQ;iBACnB;aACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAClE,OAAO;YACL,KAAK;YACL,KAAK,EAAE;gBACL,aAAa,EAAE,SAAS;gBACxB,qEAAqE;gBACrE,wDAAwD;gBACxD,QAAQ,EAAE,QAAQ,CAAC,IAAI,IAAI,QAAQ;aACpC;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/feeds/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAKrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAMhE;AAED,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/feeds/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAMrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAU9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAMhE;AAED,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import { githubReleasesAdapter } from "./github-releases.js";
2
2
  import { htmlAdapter } from "./html.js";
3
+ import { htmlJsAdapter } from "./html-js.js";
3
4
  import { npmRegistryAdapter } from "./npm-registry.js";
4
5
  import { rssAdapter } from "./rss.js";
5
6
  const adapters = new Map([
6
7
  [rssAdapter.kind, rssAdapter],
7
8
  [htmlAdapter.kind, htmlAdapter],
9
+ [htmlJsAdapter.kind, htmlJsAdapter],
8
10
  [githubReleasesAdapter.kind, githubReleasesAdapter],
9
11
  [npmRegistryAdapter.kind, npmRegistryAdapter],
10
12
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/feeds/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAA8B;IACpD,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC;IAC7B,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;IAC/B,CAAC,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACnD,CAAC,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,CAAC;CAC9C,CAAC,CAAC;AAEH,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/feeds/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAGtC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAA8B;IACpD,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC;IAC7B,CAAC,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;IAC/B,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC;IACnC,CAAC,qBAAqB,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACnD,CAAC,kBAAkB,CAAC,IAAI,EAAE,kBAAkB,CAAC;CAC9C,CAAC,CAAC;AAEH,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Detection / install helpers for the optional `playwright` peer dependency
3
+ * used by the `html-js` feed adapter (ADR-0010).
4
+ *
5
+ * The `html-js` adapter resolves Playwright at fetch time via
6
+ * `await import("playwright")`, so the package is intentionally optional in
7
+ * `package.json#peerDependenciesMeta`. Two CLI surfaces need to inspect that
8
+ * resolution outside of the adapter itself:
9
+ *
10
+ * 1. `radar doctor` — proactively reports whether Playwright + Chromium are
11
+ * installed so users can fix the environment before scheduling a watch.
12
+ * 2. `radar watch run` — lazily probes Playwright on the first `html-js`
13
+ * source so a missing install does not abort the whole run; the affected
14
+ * source is skipped with an actionable error, other kinds continue.
15
+ *
16
+ * Both paths share the same probe / install helpers here to keep the install
17
+ * hint and `RADAR_AUTO_INSTALL_CHROMIUM` escape hatch in one place. The escape
18
+ * hatch exists for CI scenarios where a fresh runner has `playwright` itself
19
+ * (e.g. via `npm i`) but no browser binary on disk yet.
20
+ *
21
+ * Policy choices (intentional, not for adapter to second-guess):
22
+ *
23
+ * - We do NOT auto-install the `playwright` npm package. Global npm installs
24
+ * fail in non-obvious ways (permissions, version mismatches with the
25
+ * workspace's lockfile), so the user must run `npm i -g playwright`
26
+ * themselves and get a clear error from `npm` if it fails.
27
+ * - We DO auto-install Chromium when `RADAR_AUTO_INSTALL_CHROMIUM=1` is set
28
+ * and Playwright is present. `npx playwright install chromium` is the
29
+ * official path, idempotent, and well-supported in CI runners.
30
+ */
31
+ /**
32
+ * Minimal Playwright surface this module reasons about. We only need
33
+ * `chromium.executablePath()` (sync function returning a fs path) — the
34
+ * fetcher in `feeds/html-js.ts` keeps its own structural type for the launch
35
+ * subset it actually uses.
36
+ */
37
+ export interface PlaywrightModuleLike {
38
+ chromium: {
39
+ executablePath: () => string;
40
+ };
41
+ }
42
+ /**
43
+ * Outcome of a Playwright probe. Discriminated union so callers branch
44
+ * cleanly on the failure mode without parsing error messages.
45
+ *
46
+ * - `ok`: module loaded AND `chromium.executablePath()` points at an
47
+ * existing file on disk.
48
+ * - `module-missing`: `import("playwright")` threw (package not installed).
49
+ * - `chromium-missing`: module loaded but the executable path is absent.
50
+ * The path is included so callers can show it to the user.
51
+ */
52
+ export type PlaywrightProbeResult = {
53
+ status: "ok";
54
+ executablePath: string;
55
+ } | {
56
+ status: "module-missing";
57
+ message: string;
58
+ } | {
59
+ status: "chromium-missing";
60
+ executablePath: string;
61
+ };
62
+ /**
63
+ * Test seam: lets unit tests inject a fake importer / `pathExists` so we can
64
+ * exercise every branch (module missing, chromium missing, ok) without
65
+ * touching the real Playwright install.
66
+ *
67
+ * The real CLI never passes these; defaults are dynamic import + `fs.access`.
68
+ */
69
+ export interface ProbeOptions {
70
+ /** Replace dynamic `import("playwright")` (tests only). */
71
+ importPlaywright?: () => Promise<unknown>;
72
+ /** Replace fs existence check (tests only). */
73
+ pathExists?: (p: string) => Promise<boolean>;
74
+ }
75
+ /**
76
+ * Probe whether Playwright + Chromium are usable from this process.
77
+ *
78
+ * Order matters: we MUST surface "module missing" before attempting any
79
+ * property access — `chromium.executablePath()` would throw with a less
80
+ * actionable message ("Cannot read properties of undefined").
81
+ */
82
+ export declare function probePlaywright(options?: ProbeOptions): Promise<PlaywrightProbeResult>;
83
+ /**
84
+ * User-facing install hint emitted when Playwright (the npm package) is
85
+ * missing. The text matches the wording in `feeds/html-js.ts#loadPlaywright`
86
+ * so users see consistent guidance across `doctor` and `watch run`.
87
+ *
88
+ * `RADAR_AUTO_INSTALL_CHROMIUM` is mentioned only in the Chromium-missing
89
+ * branch — auto-installing the npm package itself is intentionally out of
90
+ * scope (see module header).
91
+ */
92
+ export declare const PLAYWRIGHT_MODULE_MISSING_HINT: string;
93
+ /**
94
+ * User-facing install hint emitted when the npm package is present but the
95
+ * Chromium binary on disk is not. Mentions the auto-install escape hatch
96
+ * since this is the branch it actually applies to.
97
+ */
98
+ export declare const CHROMIUM_MISSING_HINT: string;
99
+ /**
100
+ * Test seam for the spawn used by `installChromium`. Production passes
101
+ * the real `child_process.spawn`; tests inject a fake that returns a
102
+ * predetermined exit code without actually launching a subprocess.
103
+ */
104
+ export type InstallSpawnLike = (command: string, args: readonly string[], options: {
105
+ cwd?: string;
106
+ stdio?: "inherit" | "pipe" | "ignore";
107
+ }) => {
108
+ on(event: "close", listener: (code: number | null) => void): void;
109
+ on(event: "error", listener: (err: Error) => void): void;
110
+ };
111
+ export interface InstallChromiumOptions {
112
+ /** Working directory for the spawned `npx`. Defaults to the caller's cwd. */
113
+ cwd?: string;
114
+ /** Test seam: swap out `child_process.spawn`. */
115
+ spawnImpl?: InstallSpawnLike;
116
+ /** Sink for progress messages (defaults to console.log). */
117
+ log?: (message: string) => void;
118
+ }
119
+ /**
120
+ * Spawn `npx playwright install chromium` and resolve when it exits.
121
+ *
122
+ * Used by the `RADAR_AUTO_INSTALL_CHROMIUM=1` escape hatch. We pipe output
123
+ * through `stdio: "inherit"` so the user (or CI logs) sees Playwright's
124
+ * progress in real time — `npx playwright install` already prints
125
+ * download URLs and percentages that are helpful debugging signal when the
126
+ * install fails. Resolves to the child's exit code so callers can decide
127
+ * whether to retry the original operation.
128
+ *
129
+ * Note we explicitly use `npx` (not direct binary lookup) because Playwright
130
+ * does not expose a JS API for browser install; the CLI is the supported
131
+ * entrypoint per Playwright docs.
132
+ */
133
+ export declare function installChromium(options?: InstallChromiumOptions): Promise<number>;
134
+ //# sourceMappingURL=playwright-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-check.d.ts","sourceRoot":"","sources":["../../src/core/playwright-check.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE;QACR,cAAc,EAAE,MAAM,MAAM,CAAC;KAC9B,CAAC;CACH;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,+CAA+C;IAC/C,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAWD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAgChG;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,QAE0B,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,QAEmC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;CAAE,KAC7D;IACH,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAAC;IAClE,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;CAC1D,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,4DAA4D;IAC5D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,CAc3F"}
@@ -0,0 +1,98 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access } from "node:fs/promises";
3
+ async function defaultPathExists(p) {
4
+ try {
5
+ await access(p);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ /**
13
+ * Probe whether Playwright + Chromium are usable from this process.
14
+ *
15
+ * Order matters: we MUST surface "module missing" before attempting any
16
+ * property access — `chromium.executablePath()` would throw with a less
17
+ * actionable message ("Cannot read properties of undefined").
18
+ */
19
+ export async function probePlaywright(options = {}) {
20
+ const importPlaywright = options.importPlaywright ?? (() => import("playwright"));
21
+ const pathExists = options.pathExists ?? defaultPathExists;
22
+ let mod;
23
+ try {
24
+ mod = (await importPlaywright());
25
+ }
26
+ catch (e) {
27
+ return {
28
+ status: "module-missing",
29
+ message: e instanceof Error ? e.message : String(e),
30
+ };
31
+ }
32
+ let execPath;
33
+ try {
34
+ execPath = mod.chromium.executablePath();
35
+ }
36
+ catch (e) {
37
+ // `executablePath()` raises when no browsers were ever installed via
38
+ // `npx playwright install`. Treat as the same user-actionable failure as a
39
+ // missing file on disk; surface an empty path so callers know the message
40
+ // came from this branch.
41
+ return {
42
+ status: "chromium-missing",
43
+ executablePath: e instanceof Error ? `(${e.message})` : "(unknown)",
44
+ };
45
+ }
46
+ if (!(await pathExists(execPath))) {
47
+ return { status: "chromium-missing", executablePath: execPath };
48
+ }
49
+ return { status: "ok", executablePath: execPath };
50
+ }
51
+ /**
52
+ * User-facing install hint emitted when Playwright (the npm package) is
53
+ * missing. The text matches the wording in `feeds/html-js.ts#loadPlaywright`
54
+ * so users see consistent guidance across `doctor` and `watch run`.
55
+ *
56
+ * `RADAR_AUTO_INSTALL_CHROMIUM` is mentioned only in the Chromium-missing
57
+ * branch — auto-installing the npm package itself is intentionally out of
58
+ * scope (see module header).
59
+ */
60
+ export const PLAYWRIGHT_MODULE_MISSING_HINT = "Playwright is required for kind: html-js. Run: npm i -g playwright && npx playwright install chromium\n" +
61
+ "Or set RADAR_AUTO_INSTALL_CHROMIUM=1 to auto-install on next run.";
62
+ /**
63
+ * User-facing install hint emitted when the npm package is present but the
64
+ * Chromium binary on disk is not. Mentions the auto-install escape hatch
65
+ * since this is the branch it actually applies to.
66
+ */
67
+ export const CHROMIUM_MISSING_HINT = "Chromium binary not found. Run: npx playwright install chromium\n" +
68
+ "Or set RADAR_AUTO_INSTALL_CHROMIUM=1 to auto-install on next run.";
69
+ /**
70
+ * Spawn `npx playwright install chromium` and resolve when it exits.
71
+ *
72
+ * Used by the `RADAR_AUTO_INSTALL_CHROMIUM=1` escape hatch. We pipe output
73
+ * through `stdio: "inherit"` so the user (or CI logs) sees Playwright's
74
+ * progress in real time — `npx playwright install` already prints
75
+ * download URLs and percentages that are helpful debugging signal when the
76
+ * install fails. Resolves to the child's exit code so callers can decide
77
+ * whether to retry the original operation.
78
+ *
79
+ * Note we explicitly use `npx` (not direct binary lookup) because Playwright
80
+ * does not expose a JS API for browser install; the CLI is the supported
81
+ * entrypoint per Playwright docs.
82
+ */
83
+ export async function installChromium(options = {}) {
84
+ const spawnImpl = options.spawnImpl ?? spawn;
85
+ const log = options.log ?? ((m) => console.log(m));
86
+ log("Installing Chromium via `npx playwright install chromium`...");
87
+ return new Promise((resolve, reject) => {
88
+ const child = spawnImpl("npx", ["playwright", "install", "chromium"], {
89
+ cwd: options.cwd,
90
+ stdio: "inherit",
91
+ });
92
+ child.on("error", reject);
93
+ child.on("close", (code) => {
94
+ resolve(code ?? 1);
95
+ });
96
+ });
97
+ }
98
+ //# sourceMappingURL=playwright-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-check.js","sourceRoot":"","sources":["../../src/core/playwright-check.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { Item, Source, SourceState } from "../schemas/index.js";
2
2
  import type { FeedAdapter, FetchLike } from "./feeds/index.js";
3
+ import { installChromium, type ProbeOptions } from "./playwright-check.js";
3
4
  export interface WorkspacePaths {
4
5
  /** Workspace root; defaults to process.cwd() at the CLI layer. */
5
6
  cwd: string;
@@ -24,6 +25,22 @@ export interface WatchRunOptions extends WorkspacePaths {
24
25
  log?: (message: string) => void;
25
26
  warn?: (message: string) => void;
26
27
  error?: (message: string) => void;
28
+ /**
29
+ * Override `process.env` lookup. Tests use this to toggle
30
+ * `RADAR_AUTO_INSTALL_CHROMIUM=1` without poking at the real environment.
31
+ */
32
+ env?: NodeJS.ProcessEnv;
33
+ /**
34
+ * Test seam: replace the Playwright probe used by the lazy `html-js`
35
+ * pre-check. Production callers leave this unset and the real
36
+ * `import("playwright")` path runs.
37
+ */
38
+ playwrightProbeOptions?: ProbeOptions;
39
+ /**
40
+ * Test seam: replace the auto-install function. Tests inject a stub that
41
+ * records invocation without actually spawning `npx playwright install`.
42
+ */
43
+ installChromiumImpl?: typeof installChromium;
27
44
  }
28
45
  export interface WatchRunResult {
29
46
  /** Map of sourceId → detected (filter-passing, not previously seen) items. */
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA+C/D,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,WAAW,CAAC;IACnD,yCAAyC;IACzC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,6EAA6E;IAC7E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAWD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CA6BnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAkHhF;AAED;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAQ9D"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAK/D,OAAO,EAEL,eAAe,EAGf,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AA2C/B,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,WAAW,CAAC;IACnD,yCAAyC;IACzC,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,YAAY,CAAC;IACtC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,eAAe,CAAC;CAC9C;AAED,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACjC,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,6EAA6E;IAC7E,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAWD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CA6BnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA+KhF;AAED;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAQ9D"}
@@ -6,6 +6,7 @@ import { getFeedAdapter } from "./feeds/index.js";
6
6
  import { filterItems } from "./filter.js";
7
7
  import { detectInjection } from "./injection-detector.js";
8
8
  import { saveItems } from "./items.js";
9
+ import { CHROMIUM_MISSING_HINT, installChromium, PLAYWRIGHT_MODULE_MISSING_HINT, probePlaywright, } from "./playwright-check.js";
9
10
  import { loadSourceState, saveSourceState } from "./state.js";
10
11
  async function pathExists(p) {
11
12
  try {
@@ -118,6 +119,8 @@ export async function watchRun(options) {
118
119
  const warn = options.warn ?? ((m) => console.warn(m));
119
120
  const error = options.error ?? ((m) => console.error(m));
120
121
  const getAdapter = options.getAdapter ?? getFeedAdapter;
122
+ const env = options.env ?? process.env;
123
+ const installImpl = options.installChromiumImpl ?? installChromium;
121
124
  const sources = await loadSources(paths.sourcesDir, (m) => warn(`watch run: ${m}`));
122
125
  const filtered = options.sourceId ? sources.filter((s) => s.id === options.sourceId) : sources;
123
126
  if (filtered.length === 0) {
@@ -130,6 +133,41 @@ export async function watchRun(options) {
130
133
  return { detected: {}, states: {}, errors: [] };
131
134
  }
132
135
  const result = { detected: {}, states: {}, errors: [] };
136
+ // Lazy Playwright probe cache. We only run the probe when the first
137
+ // `html-js` source comes up so RSS / GitHub / npm-only workspaces never pay
138
+ // for the dynamic import. The result is reused across every subsequent
139
+ // `html-js` source in the same run — both because the install state cannot
140
+ // realistically change mid-run and because re-probing per source would be
141
+ // wasteful (and would spawn `npx playwright install` repeatedly when the
142
+ // auto-install hatch is on but fails for some reason).
143
+ let playwrightProbe = null;
144
+ const ensurePlaywrightReady = async () => {
145
+ if (playwrightProbe !== null)
146
+ return playwrightProbe;
147
+ playwrightProbe = await probePlaywright(options.playwrightProbeOptions);
148
+ // Auto-install escape hatch (CI-friendly, see playwright-check.ts policy).
149
+ // Triggered only when (a) Playwright itself is present, (b) Chromium is
150
+ // missing, and (c) the user opted in via env. We re-probe after install
151
+ // so the cached result reflects post-install reality; if the install
152
+ // failed the result stays at `chromium-missing` and the source is skipped
153
+ // with the usual hint.
154
+ if (playwrightProbe.status === "chromium-missing" && env.RADAR_AUTO_INSTALL_CHROMIUM === "1") {
155
+ log("watch run: RADAR_AUTO_INSTALL_CHROMIUM=1 detected — attempting to install Chromium...");
156
+ try {
157
+ const code = await installImpl({ cwd: paths.cwd, log });
158
+ if (code === 0) {
159
+ playwrightProbe = await probePlaywright(options.playwrightProbeOptions);
160
+ }
161
+ else {
162
+ warn(`watch run: chromium auto-install exited with code ${code}`);
163
+ }
164
+ }
165
+ catch (e) {
166
+ warn(`watch run: chromium auto-install failed: ${e instanceof Error ? e.message : String(e)}`);
167
+ }
168
+ }
169
+ return playwrightProbe;
170
+ };
133
171
  for (const source of filtered) {
134
172
  const previousState = await loadSourceState(paths.stateDir, source.id);
135
173
  let adapter;
@@ -142,6 +180,27 @@ export async function watchRun(options) {
142
180
  result.errors.push({ sourceId: source.id, message });
143
181
  continue;
144
182
  }
183
+ // Lazy Playwright pre-check for `html-js` sources. Skipping the source
184
+ // (rather than aborting the whole run) preserves the contract that one
185
+ // misbehaving source must not block the others — the same shape used for
186
+ // adapter errors / fetch failures above. The error message embeds the
187
+ // canonical install hint so the user sees the same wording here as in
188
+ // `radar doctor`.
189
+ if (source.kind === "html-js") {
190
+ const probe = await ensurePlaywrightReady();
191
+ if (probe.status !== "ok") {
192
+ const hint = probe.status === "module-missing"
193
+ ? PLAYWRIGHT_MODULE_MISSING_HINT
194
+ : CHROMIUM_MISSING_HINT;
195
+ const detail = probe.status === "module-missing"
196
+ ? "playwright module not installed"
197
+ : `chromium binary missing at '${probe.executablePath}'`;
198
+ const message = `${detail}\n${hint}`;
199
+ error(`watch run: '${source.id}' skipped: ${message}`);
200
+ result.errors.push({ sourceId: source.id, message });
201
+ continue;
202
+ }
203
+ }
145
204
  let fetched;
146
205
  let nextStatePatch;
147
206
  let notModified = false;