browser-debugger-cli 0.8.0 → 0.9.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.
Files changed (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -1,28 +1,15 @@
1
1
  import { OutputFormatter } from '../formatting.js';
2
- import { evalFrameLine, frameLabel, moreMatchesNote, noFramesMessage, } from '../messages/commands.js';
3
- import { parseSelectorFilters } from '../../utils/selectorFilters.js';
2
+ import { frameLabel, moreMatchesNote, framesStillLoadingNote, noFramesMessage, queryNextSteps, screenshotGrownNote, viewportPositionHint, } from '../messages/commands.js';
4
3
  /** Matches listed in human output (JSON has all of them) */
5
4
  const QUERY_DISPLAY_LIMIT = 50;
6
- /**
7
- * A shell-quoted `dom eval` script reading the text (or a field's value) of
8
- * the n-th match in the main document, as rendered (null for matches inside iframes or
9
- * shadow roots, which `document.querySelectorAll` does not reach).
10
- *
11
- * @param selector - CSS selector
12
- * @param index - Match to read
13
- * @returns Single-quoted script, safe to paste into a shell
14
- */
15
- function textExtractionScript(selector, index) {
16
- const script = `(el => el && (el.value ?? el.innerText))(document.querySelectorAll(${JSON.stringify(selector)})[${index}])`;
17
- return `'${script.replace(/'/g, `'\\''`)}'`;
18
- }
19
5
  /**
20
6
  * Format DOM query results for human-readable output.
21
7
  *
22
- * Displays found nodes with their index, tag, classes, and preview text,
23
- * up to {@link QUERY_DISPLAY_LIMIT} of them (no match is an error, exit 83).
24
- * The text extraction hint is left out for selectors with text or visibility
25
- * filters, which `document.querySelectorAll` does not understand.
8
+ * Displays found nodes with their index, tag, classes, and preview text
9
+ * (plus where they are when outside the viewport or hidden, e.g.
10
+ * `(below fold)`), up to {@link QUERY_DISPLAY_LIMIT} of them (no match is an error, exit 83).
11
+ * One line of next commands follows; they take the match's index, so they
12
+ * work for matches in shadow roots and iframes too.
26
13
  *
27
14
  * @param data - DOM query result containing selector, count, and matching nodes
28
15
  * @returns Formatted output string
@@ -51,27 +38,23 @@ export function formatDomQuery(data) {
51
38
  node.id && ` id="${node.id}"`,
52
39
  node.name && ` name="${node.name}"`,
53
40
  node.type && ` type="${node.type}"`,
41
+ node.value !== undefined && ` value="${node.value}"`,
54
42
  node.classes?.length && ` class="${node.classes.join(' ')}"`,
55
43
  ]
56
44
  .filter(Boolean)
57
45
  .join('');
58
46
  const context = node.context ? ` (in ${node.context})` : '';
59
47
  const preview = node.preview ? ` ${node.preview}` : '';
60
- return `[${node.index}] <${node.tag}${attributes}>${context}${preview}`;
48
+ const position = node.inViewport && viewportPositionHint(node.inViewport, node.clippedBy);
49
+ const location = position ? ` (${position})` : '';
50
+ return `[${node.index}] <${node.tag}${attributes}>${context}${preview}${location}`;
61
51
  });
62
- const hasMultipleResults = count > 1;
63
- const exampleIndex = hasMultipleResults ? (nodes[0]?.index ?? 0) : 0;
52
+ const exampleIndex = nodes[0]?.index ?? 0;
64
53
  return fmt
65
54
  .text(`Found ${count} node${count === 1 ? '' : 's'} matching "${selector}":`)
66
55
  .list(nodeLines)
67
56
  .list(count > QUERY_DISPLAY_LIMIT ? [moreMatchesNote(count - QUERY_DISPLAY_LIMIT)] : [])
68
- .hints('Next steps:', [
69
- `Get HTML: bdg dom get ${exampleIndex} --raw`,
70
- `Accessibility: bdg dom get ${exampleIndex}`,
71
- ...(parseSelectorFilters(selector)
72
- ? []
73
- : [`Extract text: bdg dom eval ${textExtractionScript(selector, exampleIndex)}`]),
74
- ])
57
+ .tip(queryNextSteps(exampleIndex))
75
58
  .build();
76
59
  }
77
60
  /**
@@ -118,18 +101,23 @@ export function formatDomGet(data) {
118
101
  /**
119
102
  * Format DOM eval results for human-readable output.
120
103
  *
121
- * Outputs the evaluated JavaScript result as formatted JSON, preceded by a
122
- * `Frame: <url>` line when it ran in an iframe (`--frame`).
104
+ * A string result is printed as is (not JSON-quoted), so text reads and
105
+ * pipes like `echo`, unless that would read as another value (empty,
106
+ * `undefined`, or valid JSON such as `42` or `[1,2]`): then it stays
107
+ * JSON-quoted. Other values are formatted JSON, and values Chrome only
108
+ * describes (functions, DOM nodes) their description. The iframe it ran in
109
+ * (`--frame`) is reported on stderr, so stdout stays the bare value. `--json`
110
+ * output is unchanged (the value in `data.result`).
123
111
  *
124
- * @param data - DOM eval result containing the evaluated value (and its frame)
125
- * @returns Formatted JSON string
112
+ * @param data - DOM eval result containing the evaluated value
113
+ * @returns The string, or formatted JSON
126
114
  *
127
115
  * @example
128
116
  * ```typescript
129
- * formatDomEval({ result: 'My Page Title' });
130
- * // Output: "My Page Title"
117
+ * formatDomEval({ result: 'My Page Title', type: 'string' });
118
+ * // Output: My Page Title
131
119
  *
132
- * formatDomEval({ result: { url: 'https://example.com', title: 'Example' } });
120
+ * formatDomEval({ result: { url: 'https://example.com', title: 'Example' }, type: 'object' });
133
121
  * // Output:
134
122
  * // {
135
123
  * // "url": "https://example.com",
@@ -138,39 +126,58 @@ export function formatDomGet(data) {
138
126
  * ```
139
127
  */
140
128
  export function formatDomEval(data) {
141
- const value = formatEvalValue(data);
142
- return data.frame === undefined ? value : `${evalFrameLine(data.frame)}\n${value}`;
129
+ if (data.type === 'undefined')
130
+ return 'undefined';
131
+ const { result } = data;
132
+ if (typeof result === 'string' && (data.type !== 'string' || !looksLikeOtherValue(result))) {
133
+ return result;
134
+ }
135
+ return JSON.stringify(result ?? null, null, 2);
143
136
  }
144
137
  /**
145
- * Format an eval result value: descriptions as is, JSON values pretty-printed.
138
+ * Whether a string printed as is would read as another value: empty,
139
+ * `undefined`, or valid JSON (`42`, `true`, `null`, `[1,2]`, `{"a":1}`).
146
140
  *
147
- * @param data - Result value and its JavaScript type
148
- * @returns Formatted value
141
+ * @param text - String result
142
+ * @returns True when it must be JSON-quoted to read as a string
149
143
  */
150
- function formatEvalValue(data) {
151
- if (data.type === 'undefined')
152
- return 'undefined';
153
- const isDescription = typeof data.result === 'string' && data.type !== 'string';
154
- if (isDescription)
155
- return data.result;
156
- return JSON.stringify(data.result ?? null, null, 2);
144
+ function looksLikeOtherValue(text) {
145
+ if (text === '' || text === 'undefined')
146
+ return true;
147
+ try {
148
+ JSON.parse(text);
149
+ return true;
150
+ }
151
+ catch {
152
+ return false;
153
+ }
157
154
  }
158
155
  /**
159
- * Format the page's iframes, one per line.
156
+ * Format the page's iframes, one per line, nested frames indented below
157
+ * their parent. While the page is still loading (`readyState` set), the list
158
+ * may be incomplete, and says so.
160
159
  *
161
- * @param data - Frames from `bdg dom frames`
160
+ * @param data - Frames from `bdg dom frames`, with the readyState of a page still loading
162
161
  * @returns Formatted list
163
162
  *
164
163
  * @example
165
164
  * ```
166
165
  * [0] http://localhost:3000/widget name=widget same-origin
167
166
  * [1] https://pay.example/ #checkout cross-origin, out-of-process
167
+ * [2] about:blank same-origin
168
168
  * ```
169
169
  */
170
170
  export function formatDomFrames(data) {
171
+ const loading = data.readyState !== undefined && data.readyState !== 'complete';
171
172
  if (data.frames.length === 0)
172
- return noFramesMessage();
173
- return data.frames.map(frameLabel).join('\n');
173
+ return loading ? framesStillLoadingNote(true) : noFramesMessage();
174
+ const depthOf = new Map();
175
+ const lines = data.frames.map((frame) => {
176
+ const depth = frame.parentIndex === undefined ? 0 : (depthOf.get(frame.parentIndex) ?? 0) + 1;
177
+ depthOf.set(frame.index, depth);
178
+ return ' '.repeat(depth) + frameLabel(frame);
179
+ });
180
+ return [...lines, ...(loading ? [framesStillLoadingNote(false)] : [])].join('\n');
174
181
  }
175
182
  /**
176
183
  * Format screenshot capture result for human-readable display.
@@ -187,6 +194,9 @@ export function formatDomFrames(data) {
187
194
  *
188
195
  * formatDomScreenshot({ path: './page.png', fullPageSkipped: { reason: 'page_too_tall', ... } });
189
196
  * // Output: Screenshot saved to ./page.png (viewport only - page too tall)
197
+ *
198
+ * // An element whose floated children overflow it
199
+ * // Output: Screenshot saved to ./el.png (grown from 940×37 to 940×285 to include content overflowing the element)
190
200
  * ```
191
201
  */
192
202
  export function formatDomScreenshot(data) {
@@ -194,6 +204,9 @@ export function formatDomScreenshot(data) {
194
204
  if (data.fullPageSkipped && !data.scrolledTo) {
195
205
  output += ' (viewport only - page too tall)';
196
206
  }
207
+ if (data.element?.captured) {
208
+ output += ` (${screenshotGrownNote(data.element.bounds, data.element.captured)})`;
209
+ }
197
210
  return output;
198
211
  }
199
212
  //# sourceMappingURL=dom.js.map
@@ -5,6 +5,7 @@
5
5
  * values, validation state, and suggested commands.
6
6
  */
7
7
  import { OutputFormatter, areHintsHidden } from '../formatting.js';
8
+ import { REQUIRED_FIELD_EMPTY_REASON, formReadinessMessage, requiredFieldsEmptyMessage, } from '../messages/commands.js';
8
9
  const COLUMN_WIDTHS = {
9
10
  index: 4,
10
11
  type: 12,
@@ -64,6 +65,9 @@ function formatFieldValue(field) {
64
65
  * @returns Status string with icon
65
66
  */
66
67
  function formatFieldStatus(field) {
68
+ if (field.hidden) {
69
+ return 'hidden';
70
+ }
67
71
  if (field.disabled) {
68
72
  return 'disabled';
69
73
  }
@@ -127,12 +131,23 @@ function formatButtonRow(button) {
127
131
  * @returns Header string
128
132
  */
129
133
  function formatFormHeader(form) {
130
- let header = `Form: "${form.name ?? 'Unnamed'}"`;
134
+ let header = `Form: "${form.name ?? 'Unnamed'}"${formVisibilityMarker(form)}`;
131
135
  if (form.step) {
132
136
  header += ` (step ${form.step.current} of ${form.step.total})`;
133
137
  }
134
138
  return header;
135
139
  }
140
+ /**
141
+ * Marker of a form shown in an open dialog or not shown at all.
142
+ *
143
+ * @param form - Form (or its listing in "Other forms")
144
+ * @returns ` (in dialog)`, ` (hidden)`, or empty
145
+ */
146
+ function formVisibilityMarker(form) {
147
+ if (form.hidden)
148
+ return ' (hidden)';
149
+ return form.inDialog ? ' (in dialog)' : '';
150
+ }
136
151
  /**
137
152
  * Format table header row.
138
153
  *
@@ -147,7 +162,9 @@ function formatTableHeader() {
147
162
  return `${idx} ${type} ${label} ${value} ${status}`;
148
163
  }
149
164
  /**
150
- * Format summary line.
165
+ * Format summary line, e.g. "Summary: 1/3 fields filled | 2 required fields
166
+ * empty: Last Name, Zip | NOT ready" (a radio or checkbox group counts as one
167
+ * field).
151
168
  *
152
169
  * @param summary - Form summary
153
170
  * @returns Summary string
@@ -158,15 +175,12 @@ function formatSummaryLine(summary) {
158
175
  if (summary.invalidFields > 0) {
159
176
  parts.push(`${summary.invalidFields} invalid`);
160
177
  }
161
- if (summary.requiredRemaining > 0) {
162
- parts.push(`${summary.requiredRemaining} required remaining`);
163
- }
164
- if (summary.readyToSubmit) {
165
- parts.push('READY to submit');
166
- }
167
- else {
168
- parts.push('NOT ready');
169
- }
178
+ const requiredEmpty = summary.blockers
179
+ .filter((blocker) => blocker.reason === REQUIRED_FIELD_EMPTY_REASON)
180
+ .map((blocker) => blocker.label);
181
+ if (requiredEmpty.length > 0)
182
+ parts.push(requiredFieldsEmptyMessage(requiredEmpty));
183
+ parts.push(formReadinessMessage(summary));
170
184
  return `Summary: ${parts.join(' | ')}`;
171
185
  }
172
186
  /**
@@ -197,9 +211,7 @@ function formatSingleForm(form, fmt, brief = false) {
197
211
  fmt.text(formatTableHeader());
198
212
  fmt.text('─'.repeat(70));
199
213
  for (const field of form.fields) {
200
- if (!field.hidden) {
201
- fmt.text(formatFieldRow(field));
202
- }
214
+ fmt.text(formatFieldRow(field));
203
215
  }
204
216
  if (form.buttons.length > 0) {
205
217
  fmt.text('─'.repeat(70));
@@ -228,13 +240,12 @@ function formatBriefFields(form, fmt) {
228
240
  fmt.text('IDX TYPE LABEL REQ VALUE');
229
241
  fmt.text('─'.repeat(70));
230
242
  for (const field of form.fields) {
231
- if (field.hidden)
232
- continue;
233
243
  const idx = `[${field.index}]`.padEnd(4);
234
244
  const type = (field.inputType ?? field.type).slice(0, 11).padEnd(12);
235
245
  const label = (field.label ?? field.name ?? '(no label)').slice(0, 23).padEnd(24);
236
246
  const req = (field.required ? '*' : '').padEnd(3);
237
- fmt.text(`${idx} ${type} ${label} ${req} ${formatFieldValue(field)}`);
247
+ const hidden = field.hidden ? ' (hidden)' : '';
248
+ fmt.text(`${idx} ${type} ${label} ${req} ${formatFieldValue(field)}${hidden}`);
238
249
  }
239
250
  if (form.buttons.length > 0) {
240
251
  fmt.text('─'.repeat(70));
@@ -258,7 +269,7 @@ function formatOtherForms(others, fmt) {
258
269
  fmt.blank();
259
270
  fmt.text('Other forms on page:');
260
271
  for (const form of others) {
261
- fmt.text(` Form ${form.index}: "${form.name ?? 'Unnamed'}" - ${form.fieldCount} field(s)`);
272
+ fmt.text(` Form ${form.index}: "${form.name ?? 'Unnamed'}"${formVisibilityMarker(form)} - ${form.fieldCount} field(s)`);
262
273
  }
263
274
  fmt.blank();
264
275
  fmt.text('Use --all to see all forms');
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Human-readable output of `bdg dom layout`.
3
+ */
4
+ import type { ElementLayout, LayoutResult, LayoutSize } from '../../ipc/protocol/domTypes.js';
5
+ import type { IndexSource } from '../../types.js';
6
+ /** `bdg dom layout` data (the `success` flag is implied by the envelope), with the list an index refers to */
7
+ type LayoutOutput = Omit<LayoutResult, 'success'> & {
8
+ indexSource?: IndexSource | undefined;
9
+ };
10
+ /**
11
+ * One element as a compact line: index, description, text, page position
12
+ * and size (not for hidden elements, which have no meaningful box), where it
13
+ * is relative to the viewport, what covers it, an inert or invisible element
14
+ * and the iframe or shadow root it is in (unless the position already names
15
+ * it as the clipping iframe).
16
+ *
17
+ * @param element - Element layout
18
+ * @param viewport - Viewport size, when known (an element larger than it is not centred by the scroll)
19
+ * @returns e.g. `[0] button#save "Save" 420,1180 120×40 below fold (scroll down 500px to centre it)`
20
+ */
21
+ export declare function layoutLine(element: ElementLayout, viewport?: LayoutSize): string;
22
+ /**
23
+ * Format `bdg dom layout` output: the page dimensions, then one line per
24
+ * element (the first {@link LAYOUT_DISPLAY_LIMIT}).
25
+ *
26
+ * @param data - Layout report
27
+ * @returns Formatted output
28
+ */
29
+ export declare function formatLayout(data: LayoutOutput): string;
30
+ export {};
31
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Human-readable output of `bdg dom layout`.
3
+ */
4
+ import { indexSourceText } from '../../errors/messages.js';
5
+ import { OutputFormatter } from '../formatting.js';
6
+ import { indexLayoutHeadline, layoutHeadline, layoutPositionLabel, moreMatchesNote, pageLayoutLine, } from '../messages/commands.js';
7
+ /** Elements listed in human output (JSON has up to 100) */
8
+ const LAYOUT_DISPLAY_LIMIT = 20;
9
+ /**
10
+ * One element as a compact line: index, description, text, page position
11
+ * and size (not for hidden elements, which have no meaningful box), where it
12
+ * is relative to the viewport, what covers it, an inert or invisible element
13
+ * and the iframe or shadow root it is in (unless the position already names
14
+ * it as the clipping iframe).
15
+ *
16
+ * @param element - Element layout
17
+ * @param viewport - Viewport size, when known (an element larger than it is not centred by the scroll)
18
+ * @returns e.g. `[0] button#save "Save" 420,1180 120×40 below fold (scroll down 500px to centre it)`
19
+ */
20
+ export function layoutLine(element, viewport) {
21
+ const { bounds } = element;
22
+ return [
23
+ `[${element.index}] ${element.element}${element.text ? ` "${element.text}"` : ''}`,
24
+ element.inViewport !== 'hidden' && `${bounds.x},${bounds.y} ${bounds.width}×${bounds.height}`,
25
+ layoutPositionLabel(element, viewport),
26
+ element.coveredBy && `covered by ${element.coveredBy}`,
27
+ element.inert && 'inert',
28
+ element.invisible,
29
+ element.context && element.context !== element.clippedBy && `in ${element.context}`,
30
+ ]
31
+ .filter(Boolean)
32
+ .join(' ');
33
+ }
34
+ /**
35
+ * Format `bdg dom layout` output: the page dimensions, then one line per
36
+ * element (the first {@link LAYOUT_DISPLAY_LIMIT}).
37
+ *
38
+ * @param data - Layout report
39
+ * @returns Formatted output
40
+ */
41
+ export function formatLayout(data) {
42
+ const shown = data.elements.slice(0, LAYOUT_DISPLAY_LIMIT);
43
+ const more = data.elements.length - shown.length + (data.omitted ?? 0);
44
+ return new OutputFormatter()
45
+ .text(pageLayoutLine(data.page))
46
+ .text(data.indexSource
47
+ ? indexLayoutHeadline(indexSourceText(data.indexSource))
48
+ : layoutHeadline(data.count, data.elements.length + (data.omitted ?? 0), data.selector))
49
+ .list(shown.map((element) => layoutLine(element, data.page.viewport)))
50
+ .list(more > 0 ? [moreMatchesNote(more, data.omitted ? data.elements.length : undefined)] : [])
51
+ .build();
52
+ }
53
+ //# sourceMappingURL=layout.js.map
@@ -12,8 +12,9 @@ type ListenersOutput = Omit<ListenersResult, 'success'>;
12
12
  */
13
13
  export declare function handlerLocation(handler: ElementListener['handler']): string;
14
14
  /**
15
- * Format `bdg dom listeners` output: listeners grouped by event type,
16
- * nearest first, with a note for events handled only by delegation.
15
+ * Format `bdg dom listeners` output: listeners grouped by event type, the
16
+ * handlers nearest the element first, then one line per framework root,
17
+ * with notes for events the element has no listener of its own for.
17
18
  *
18
19
  * @param result - Listener report
19
20
  * @param types - Event types asked for with --type, if any
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Human-readable output of `bdg dom listeners`.
3
3
  */
4
- import { delegatedOnlyTypes } from '../../runtime/dom/listenerSummary.js';
4
+ import { delegationNotes } from '../../runtime/dom/listenerSummary.js';
5
5
  import { OutputFormatter } from '../formatting.js';
6
- import { NO_LISTENERS_HINT, delegatedListenersNote, listenersHeadline, noListenersMessage, } from '../messages/commands.js';
6
+ import { COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, collapsedListenersSummary, delegationNote, eventTypeSuggestion, jqueryHandlersSkippedNote, listenersHeadline, noListenersMessage, reactHandlersSkippedNote, } from '../messages/commands.js';
7
7
  import { truncateByLength } from '../../utils/strings.js';
8
8
  /** Longest handler name shown (JSON has the full name) */
9
9
  const NAME_MAX_LENGTH = 30;
@@ -29,6 +29,9 @@ export function handlerLocation(handler) {
29
29
  */
30
30
  function listenerCells(listener) {
31
31
  const flags = [
32
+ listener.reactProp ? `React ${listener.reactProp}` : listener.framework,
33
+ listener.delegateSelector && `delegate ${listener.delegateSelector}`,
34
+ listener.noop && 'no-op',
32
35
  listener.useCapture && 'capture',
33
36
  listener.passive && 'passive',
34
37
  listener.once && 'once',
@@ -54,9 +57,31 @@ function alignColumns(rows) {
54
57
  .map((cell, column) => (column === row.length - 1 ? cell : cell.padEnd(widths[column] ?? 0)))
55
58
  .join(' '));
56
59
  }
60
+ /** Dispatcher names listed per collapsed framework root */
61
+ const COLLAPSED_HANDLERS_SHOWN = 3;
57
62
  /**
58
- * Format `bdg dom listeners` output: listeners grouped by event type,
59
- * nearest first, with a note for events handled only by delegation.
63
+ * Listeners by where they are attached, collapsed framework roots included.
64
+ *
65
+ * @param listeners - Listed listeners
66
+ * @param collapsed - Collapsed framework roots
67
+ * @returns Counts on the element, its ancestors, and document or window
68
+ */
69
+ function listenerCounts(listeners, collapsed) {
70
+ const counts = { target: 0, ancestor: 0, global: 0 };
71
+ const add = (on, count) => {
72
+ if (on === 'target' || on === 'ancestor')
73
+ counts[on] += count;
74
+ else
75
+ counts.global += count;
76
+ };
77
+ listeners.forEach((listener) => add(listener.on, 1));
78
+ collapsed.forEach((root) => add(root.on, root.count));
79
+ return counts;
80
+ }
81
+ /**
82
+ * Format `bdg dom listeners` output: listeners grouped by event type, the
83
+ * handlers nearest the element first, then one line per framework root,
84
+ * with notes for events the element has no listener of its own for.
60
85
  *
61
86
  * @param result - Listener report
62
87
  * @param types - Event types asked for with --type, if any
@@ -64,21 +89,60 @@ function alignColumns(rows) {
64
89
  */
65
90
  export function formatListeners(result, types) {
66
91
  const fmt = new OutputFormatter();
67
- if (result.listeners.length === 0) {
92
+ const collapsed = result.collapsed ?? [];
93
+ if (result.listeners.length === 0 && collapsed.length === 0) {
68
94
  fmt.text(noListenersMessage(result.element, types));
95
+ if (result.typeSuggestions?.length)
96
+ fmt.text(eventTypeSuggestion(result.typeSuggestions));
69
97
  fmt.tip(NO_LISTENERS_HINT);
70
98
  }
71
99
  else {
72
- fmt.text(listenersHeadline(result.element, result.listeners.length));
100
+ fmt.text(listenersHeadline(result.element, listenerCounts(result.listeners, collapsed), result));
73
101
  appendListenerGroups(fmt, result.listeners);
74
- const delegated = delegatedOnlyTypes(result.listeners);
75
- if (delegated.length > 0)
76
- fmt.blank().text(delegatedListenersNote(delegated));
102
+ appendCollapsed(fmt, collapsed);
103
+ appendDelegationNotes(fmt, result.listeners, collapsed);
104
+ }
105
+ if (result.jqueryHandlersSkipped) {
106
+ fmt.blank().text(jqueryHandlersSkippedNote(result.jqueryHandlersSkipped));
107
+ }
108
+ if (result.reactHandlersSkipped) {
109
+ fmt.blank().text(reactHandlersSkippedNote(result.reactHandlersSkipped));
77
110
  }
78
111
  if (result.warning)
79
112
  fmt.blank().text(`⚠ Warning: ${result.warning}`);
80
113
  return fmt.build();
81
114
  }
115
+ /**
116
+ * Add a note per way events reach their handlers without a listener on the
117
+ * element itself (React props, React root, jQuery, plain delegation).
118
+ *
119
+ * @param fmt - Output being built
120
+ * @param listeners - Listeners
121
+ * @param collapsed - Collapsed roots
122
+ */
123
+ function appendDelegationNotes(fmt, listeners, collapsed) {
124
+ const notes = delegationNotes(listeners, collapsed).flatMap((note) => delegationNote(note) ?? []);
125
+ if (notes.length > 0)
126
+ fmt.blank();
127
+ notes.forEach((note) => fmt.text(note));
128
+ }
129
+ /**
130
+ * Add the collapsed framework roots, one line per node.
131
+ *
132
+ * @param fmt - Output being built
133
+ * @param collapsed - Collapsed roots
134
+ */
135
+ function appendCollapsed(fmt, collapsed) {
136
+ if (collapsed.length === 0)
137
+ return;
138
+ fmt.blank().text(COLLAPSED_LISTENERS_HEADING);
139
+ const rows = collapsed.map((root) => {
140
+ const names = root.handlers.map((handler) => truncateByLength(handler.name || ANONYMOUS, NAME_MAX_LENGTH));
141
+ const shown = [...new Set(names)].slice(0, COLLAPSED_HANDLERS_SHOWN);
142
+ return [root.on, root.node, collapsedListenersSummary({ ...root, handlers: shown })];
143
+ });
144
+ alignColumns(rows).forEach((line) => fmt.text(` ${line}`));
145
+ }
82
146
  /**
83
147
  * Add one block per event type: the type, then its listeners.
84
148
  *
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Formats network request and response headers for human-readable display.
5
5
  */
6
+ import { formatRequestStatus } from './requestStatus.js';
6
7
  import { OutputFormatter } from '../formatting.js';
7
8
  /**
8
9
  * Format network request headers for display.
@@ -14,6 +15,7 @@ export function formatNetworkHeaders(data) {
14
15
  const fmt = new OutputFormatter();
15
16
  fmt.text('Network Request Headers').separator('━', 60).blank();
16
17
  fmt.text('URL:').text(` ${data.url}`).blank();
18
+ fmt.text(`Status: ${requestStatusLine(data)}`).blank();
17
19
  if (Object.keys(data.responseHeaders).length > 0) {
18
20
  fmt.text('Response Headers:');
19
21
  formatHeaderSection(fmt, data.responseHeaders);
@@ -47,4 +49,15 @@ function formatHeaderSection(fmt, headers) {
47
49
  value.split('\n').forEach((line) => fmt.keyValue(` ${key}`, line, keyWidth));
48
50
  });
49
51
  }
52
+ /**
53
+ * Status line of a request: method, HTTP status and status text, or how it failed.
54
+ *
55
+ * @param data - Network headers data
56
+ * @returns e.g. `GET 404 Not Found`, `GET FAILED (net::ERR_NAME_NOT_RESOLVED)`, `GET pending`
57
+ */
58
+ function requestStatusLine(data) {
59
+ const status = formatRequestStatus(data);
60
+ const text = data.statusText && /^\d+$/.test(status) ? ` ${data.statusText}` : '';
61
+ return [data.method, `${status}${text}`].filter(Boolean).join(' ');
62
+ }
50
63
  //# sourceMappingURL=networkHeaders.js.map
@@ -4,6 +4,7 @@ import { formatTimestamp } from './console/shared.js';
4
4
  import { failureReason, formatRequestStatus, getRequestState, } from './requestStatus.js';
5
5
  import { OutputFormatter, truncateUrl, truncateText } from '../formatting.js';
6
6
  import { PREVIEW_EMPTY_STATES, PREVIEW_HEADERS, compactTipsMessage, verboseCommandsMessage, } from '../messages/preview.js';
7
+ import { sessionCommand } from '../messages/sessionCommand.js';
7
8
  /**
8
9
  * Infer resource type from MIME type when CDP doesn't provide it.
9
10
  *
@@ -218,7 +219,7 @@ function formatPreviewVerbose(output, options) {
218
219
  if (req.mimeType) {
219
220
  fmt.text(` MIME: ${req.mimeType}`);
220
221
  }
221
- fmt.text(` ID: ${req.requestId} (use 'bdg details network ${req.requestId}' for full details)`);
222
+ fmt.text(` ID: ${req.requestId} (use '${sessionCommand(`bdg details network ${req.requestId}`)}' for full details)`);
222
223
  });
223
224
  }
224
225
  fmt.blank();
@@ -6,23 +6,7 @@
6
6
  * response keeps its HTTP status even if loading failed afterwards.
7
7
  */
8
8
  import type { NetworkRequest } from '../../types.js';
9
- /** Lifecycle state of a captured request. */
10
- export type RequestState = 'pending' | 'failed' | 'complete';
11
- /**
12
- * Classify a request.
13
- *
14
- * @param request - Captured request
15
- * @returns Its lifecycle state
16
- */
17
- export declare function getRequestState(request: Pick<NetworkRequest, 'status'>): RequestState;
18
- /**
19
- * Why a request failed, leaving out Chrome stopping an unneeded body after a
20
- * complete response (204/304 report `net::ERR_ABORTED` that way).
21
- *
22
- * @param request - Captured request
23
- * @returns Reason, or undefined when there is none worth showing
24
- */
25
- export declare function failureReason(request: Pick<NetworkRequest, 'status' | 'errorText'>): string | undefined;
9
+ export { failureReason, getRequestState, type RequestState } from '../../telemetry/requestState.js';
26
10
  /**
27
11
  * Human-readable status: the HTTP status (with the failure reason if loading
28
12
  * failed after the response), `FAILED (<reason>)`, or `pending`.
@@ -5,31 +5,8 @@
5
5
  * without any response (DNS, refused, aborted, blocked); a request that got a
6
6
  * response keeps its HTTP status even if loading failed afterwards.
7
7
  */
8
- /**
9
- * Classify a request.
10
- *
11
- * @param request - Captured request
12
- * @returns Its lifecycle state
13
- */
14
- export function getRequestState(request) {
15
- if (request.status === undefined)
16
- return 'pending';
17
- return request.status === 0 ? 'failed' : 'complete';
18
- }
19
- /**
20
- * Why a request failed, leaving out Chrome stopping an unneeded body after a
21
- * complete response (204/304 report `net::ERR_ABORTED` that way).
22
- *
23
- * @param request - Captured request
24
- * @returns Reason, or undefined when there is none worth showing
25
- */
26
- export function failureReason(request) {
27
- if (!request.errorText)
28
- return undefined;
29
- return getRequestState(request) === 'complete' && request.errorText === BODY_ABORTED
30
- ? undefined
31
- : request.errorText;
32
- }
8
+ import { BODY_ABORTED, getRequestState } from '../../telemetry/requestState.js';
9
+ export { failureReason, getRequestState } from '../../telemetry/requestState.js';
33
10
  /**
34
11
  * Human-readable status: the HTTP status (with the failure reason if loading
35
12
  * failed after the response), `FAILED (<reason>)`, or `pending`.
@@ -47,9 +24,4 @@ export function formatRequestStatus(request) {
47
24
  ? `${request.status} (${request.errorText})`
48
25
  : String(request.status);
49
26
  }
50
- /**
51
- * Error Chrome reports when it stops reading a response body nobody needs
52
- * (the response itself arrived; not a failure of the request).
53
- */
54
- const BODY_ABORTED = 'net::ERR_ABORTED';
55
27
  //# sourceMappingURL=requestStatus.js.map
@@ -0,0 +1,12 @@
1
+ import type { RunningSessionInfo } from '../../session/sessionList.js';
2
+ /**
3
+ * Format the sessions as a table, followed by the cleanup commands of
4
+ * crashed and stale sessions.
5
+ *
6
+ * @param data - Sessions
7
+ * @returns Human-readable list
8
+ */
9
+ export declare function formatSessionList(data: {
10
+ sessions: RunningSessionInfo[];
11
+ }): string;
12
+ //# sourceMappingURL=sessions.d.ts.map