sourcemonkey 0.0.0-stage → 0.5.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 (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. package/skills/SourceMonkey-friction/references/template.md +59 -0
@@ -0,0 +1,132 @@
1
+ // Node counterpart of `src/extension/parse-sandbox.ts` — the compile-only parse
2
+ // check, run under Node's V8 instead of Chrome's. Used by the development
3
+ // harness (`prepare.ts`), which runs the install pipeline outside a
4
+ // browser for `sm-dev` and the corpus validator.
5
+ //
6
+ // The extension can only do this inside a sandboxed extension page,
7
+ // because MV3's CSP forbids `eval` everywhere else and the location
8
+ // probes need to insert a `<script>`. Node has neither restriction, so
9
+ // the same two-step check (verdict, then location) is a few lines here:
10
+ //
11
+ // - the verdict comes from `new Function(src)`, byte-for-byte the
12
+ // call the sandbox makes;
13
+ // - the location comes from compiling the same text twice more with
14
+ // `new vm.Script`, which is Node's classic-script compile — the
15
+ // same thing appending a `<script>` element does in the sandbox.
16
+ //
17
+ // Everything downstream (`selectParseLocation`, `attachParseLocation`)
18
+ // is the extension's own code, so the probe selection rule is shared
19
+ // rather than reimplemented.
20
+ //
21
+ // Caveat worth remembering when reading results: Node's V8 is not
22
+ // Chrome's V8. Both accept the same language, but a very new syntax
23
+ // feature (or a wording change in a `SyntaxError` message) can differ
24
+ // between the two versions. `node --version` pins which one answered.
25
+ import vm from 'node:vm';
26
+ // Mirrors `parse-sandbox.ts` exactly, because the probe text is what
27
+ // the reported positions are relative to: change the framing and the
28
+ // line numbers stop matching the browser's.
29
+ //
30
+ // In the sandbox the sentinel is load-bearing for safety — appending a
31
+ // `<script>` element runs it, and only a guaranteed parse failure stops
32
+ // that. `new vm.Script` compiles without running either way, so here
33
+ // the sentinel is purely about parity. Keeping it means a source that
34
+ // ends mid-construct fails at the same place in both, which is the case
35
+ // `selectParseLocation`'s range check is reading.
36
+ const PROBE_SENTINEL = '\n(';
37
+ const WRAPPER_HEAD = 'function __smParseProbe__() {\n';
38
+ const WRAPPER_TAIL = '\n}';
39
+ // Any name works; it only has to be recognizable in the stack trace we
40
+ // parse the position out of. The `:` is the separator V8 puts before
41
+ // the line number — matched as a literal prefix rather than
42
+ // interpolated into a regex, so a future rename can't turn a character
43
+ // of the name into a wildcard.
44
+ const PROBE_FILENAME = 'sm-parse-probe.js';
45
+ const PROBE_HEAD_PREFIX = `${PROBE_FILENAME}:`;
46
+ /** The verdict, with probes attached when it's a syntax error. */
47
+ export function checkParseNode(source) {
48
+ try {
49
+ // eslint-disable-next-line no-new-func
50
+ new Function(source);
51
+ return { status: 'ok' };
52
+ }
53
+ catch (err) {
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ // Only a SyntaxError means "this source is broken". A RangeError
56
+ // (parser stack overflow on pathologically nested source) means we
57
+ // couldn't tell — see the long note in parse-sandbox.ts.
58
+ if (!(err instanceof SyntaxError))
59
+ return { status: 'unknown', message };
60
+ return { status: 'syntax-error', message, probes: locate(source) };
61
+ }
62
+ }
63
+ function locate(source) {
64
+ try {
65
+ return {
66
+ wrapped: runProbe(`${WRAPPER_HEAD}${source}${WRAPPER_TAIL}${PROBE_SENTINEL}`, 1),
67
+ raw: runProbe(`${source}${PROBE_SENTINEL}`, 0),
68
+ };
69
+ }
70
+ catch {
71
+ // Locating is a nicety; the verdict is the product.
72
+ return undefined;
73
+ }
74
+ }
75
+ // Compile `text` as a classic script and report where it failed.
76
+ // `lineOffset` is how many lines of our own framing precede the source,
77
+ // so the caller gets source coordinates back.
78
+ function runProbe(text, lineOffset) {
79
+ try {
80
+ new vm.Script(text, { filename: PROBE_FILENAME });
81
+ }
82
+ catch (err) {
83
+ if (!(err instanceof SyntaxError))
84
+ return undefined;
85
+ const at = positionFromStack(err);
86
+ if (!at)
87
+ return undefined;
88
+ return { message: err.message, line: at.line - lineOffset, column: at.column };
89
+ }
90
+ // No error at all means the sentinel failed to do its job — and the
91
+ // source ran. Report no position; `selectParseLocation` then has one
92
+ // fewer probe to choose from.
93
+ return undefined;
94
+ }
95
+ // A `vm.Script` SyntaxError carries its position only in the rendered
96
+ // preamble at the top of `err.stack`, which V8 formats as:
97
+ //
98
+ // sm-parse-probe.js:3
99
+ // }
100
+ // ^
101
+ // (blank)
102
+ // SyntaxError: Unexpected token '}'
103
+ // at new Script (node:vm:…)
104
+ //
105
+ // The first line gives the 1-based line; the caret line gives the
106
+ // 1-based column as its first `^`. A stack whose first line isn't the
107
+ // preamble is one we don't recognize, and gets no position at all.
108
+ //
109
+ // **The column is best-effort; the line is not.** V8 clips the rendered
110
+ // frame at ~1020 characters, so for a longer source line the caret line
111
+ // is all spaces with no `^` in it and the true column is simply not in
112
+ // the stack. That is not exotic input here — a minified script, or a
113
+ // minified `@require`, is one enormous line, and bulk-checking real
114
+ // corpora is what this tool is for. Chrome's path reads `event.colno`
115
+ // and has the real column in every case, so this is the one place the
116
+ // Node port is knowingly less precise. We keep the line (exact, and the
117
+ // part that names a file) and fall back to column 1, which
118
+ // `COLUMN_UNKNOWN` marks as a fallback rather than a measurement.
119
+ // Nothing downstream depends on it: `selectParseLocation` compares only
120
+ // lines, so probe choice and the verdict are unaffected.
121
+ const COLUMN_UNKNOWN = 1;
122
+ function positionFromStack(err) {
123
+ const lines = String(err.stack ?? '').split('\n');
124
+ const head = lines[0] ?? '';
125
+ if (!head.startsWith(PROBE_HEAD_PREFIX))
126
+ return null;
127
+ const line = Number(head.slice(PROBE_HEAD_PREFIX.length));
128
+ if (!Number.isInteger(line))
129
+ return null;
130
+ const caretIndex = (lines[2] ?? '').indexOf('^');
131
+ return { line, column: caretIndex >= 0 ? caretIndex + 1 : COLUMN_UNKNOWN };
132
+ }
@@ -0,0 +1,22 @@
1
+ export declare const HAS_SCHEME: RegExp;
2
+ /**
3
+ * Whether a word is a glob: one with a `*` and no `scheme:` in front.
4
+ * A URL to open always has a scheme or a host with a dot, and no web
5
+ * page is at a `*`, so `example.com/*` is a glob too (and an error if
6
+ * it matches no file).
7
+ */
8
+ export declare function isGlob(word: string): boolean;
9
+ export declare function isDirectory(file: string): boolean;
10
+ /** The `*.user.js` files under `dir`, as paths starting with `spelled`. */
11
+ export declare function walkScripts(dir: string, spelled: string, out: string[]): void;
12
+ /**
13
+ * What is wrong with a glob, or null. `**` only works as a whole path
14
+ * part (not `a**` or `**.user.js`), and not as the last one, which
15
+ * would leave the file name unmatched.
16
+ */
17
+ export declare function globMistake(pattern: string): string | null;
18
+ /**
19
+ * The files a glob matches, sorted within each directory, no
20
+ * directories. The glob must have no globMistake.
21
+ */
22
+ export declare function globFiles(pattern: string, cwd?: string): string[];
@@ -0,0 +1,148 @@
1
+ // Globs and directory searches over the file system, for sm-dev's file
2
+ // arguments (see expandPathArg in checks.ts). `*` matches within one
3
+ // path segment, and a `**` segment matches any number of directories.
4
+ //
5
+ // Synchronous, so the command line can sort its words into scripts and
6
+ // URLs before deciding whether a command goes to a run.
7
+ import { readdirSync, statSync } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { quoted } from './output-style.js';
10
+ const SCRIPT_FILE = /\.user\.js$/i;
11
+ // Words with a scheme are URLs, whatever they contain. Two letters or
12
+ // more, so a Windows drive (`C:`) stays a path.
13
+ export const HAS_SCHEME = /^[a-z][a-z0-9+.-]+:/i;
14
+ // Path separators: a backslash only where it is one.
15
+ const SEPARATORS = path.sep === '\\' ? /[/\\]+/ : /\/+/;
16
+ /**
17
+ * Whether a word is a glob: one with a `*` and no `scheme:` in front.
18
+ * A URL to open always has a scheme or a host with a dot, and no web
19
+ * page is at a `*`, so `example.com/*` is a glob too (and an error if
20
+ * it matches no file).
21
+ */
22
+ export function isGlob(word) {
23
+ return word.includes('*') && !HAS_SCHEME.test(word);
24
+ }
25
+ export function isDirectory(file) {
26
+ try {
27
+ return statSync(file).isDirectory();
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ function entriesOf(dir) {
34
+ try {
35
+ return readdirSync(dir, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ // A directory searched in full, or by `**`: not node_modules, nor
42
+ // hidden ones, which hold copies and tool state rather than sources.
43
+ // Symbolic links to directories aren't followed either, so a link
44
+ // back up the tree can't loop.
45
+ const skipped = (entry) => entry.name === 'node_modules' || entry.name.startsWith('.');
46
+ /** The `*.user.js` files under `dir`, as paths starting with `spelled`. */
47
+ export function walkScripts(dir, spelled, out) {
48
+ for (const entry of entriesOf(dir)) {
49
+ if (skipped(entry))
50
+ continue;
51
+ const full = path.join(dir, entry.name);
52
+ const shown = path.join(spelled, entry.name);
53
+ if (entry.isDirectory())
54
+ walkScripts(full, shown, out);
55
+ else if (SCRIPT_FILE.test(entry.name))
56
+ out.push(shown);
57
+ }
58
+ }
59
+ // One path segment's pattern: `*` is any run of characters but `/`.
60
+ function segmentRegExp(segment) {
61
+ const body = segment.split('*').map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*');
62
+ return new RegExp(`^${body}$`);
63
+ }
64
+ /**
65
+ * What is wrong with a glob, or null. `**` only works as a whole path
66
+ * part (not `a**` or `**.user.js`), and not as the last one, which
67
+ * would leave the file name unmatched.
68
+ */
69
+ export function globMistake(pattern) {
70
+ // A trailing slash leaves an empty last part, which isn't one.
71
+ const segments = pattern.split(SEPARATORS).filter(Boolean);
72
+ if (segments.some((s) => s.includes('**') && s !== '**')) {
73
+ return `Invalid glob ${quoted(pattern)}. Use ${quoted('**')} only as a whole path part, as in ${quoted('src/**/*.user.js')}.`;
74
+ }
75
+ if (segments.at(-1) === '**') {
76
+ return `Invalid glob ${quoted(pattern)}. It ends in ${quoted('**')}, so it names no files. Add a file name, as in ${quoted(`${pattern.replace(/[/\\]+$/, '')}/*.user.js`)}.`;
77
+ }
78
+ return null;
79
+ }
80
+ /**
81
+ * The files a glob matches, sorted within each directory, no
82
+ * directories. The glob must have no globMistake.
83
+ */
84
+ export function globFiles(pattern, cwd = process.cwd()) {
85
+ if (!pattern.includes('*'))
86
+ throw new Error('Internal error: globFiles needs a pattern with a *');
87
+ // `/`, or a drive's root (`C:\\`) on Windows; '' when relative.
88
+ const root = path.parse(pattern).root;
89
+ const segments = pattern.slice(root.length).split(SEPARATORS).filter(Boolean);
90
+ // The part before the first `*` is a plain directory to start from.
91
+ const firstGlob = segments.findIndex((s) => s.includes('*'));
92
+ const base = segments.slice(0, firstGlob);
93
+ const spelledBase = root || base.length > 0 ? path.join(root, ...base) : '';
94
+ const found = [];
95
+ const visit = (dir, spelled, rest) => {
96
+ const [segment, ...after] = rest;
97
+ const join = (name) => (spelled ? path.join(spelled, name) : name);
98
+ if (segment === '**') {
99
+ // Zero directories, then one more, and so on.
100
+ visit(dir, spelled, after);
101
+ for (const entry of entriesOf(dir)) {
102
+ if (!skipped(entry) && entry.isDirectory())
103
+ visit(path.join(dir, entry.name), join(entry.name), rest);
104
+ }
105
+ return;
106
+ }
107
+ if (!segment.includes('*')) {
108
+ const full = path.join(dir, segment);
109
+ if (after.length === 0) {
110
+ if (!isDirectory(full) && statOk(full))
111
+ found.push(join(segment));
112
+ }
113
+ else if (isDirectory(full)) {
114
+ visit(full, join(segment), after);
115
+ }
116
+ return;
117
+ }
118
+ const re = segmentRegExp(segment);
119
+ for (const entry of entriesOf(dir)) {
120
+ // A hidden name only for a segment that asks for one.
121
+ if (entry.name.startsWith('.') && !segment.startsWith('.'))
122
+ continue;
123
+ if (!re.test(entry.name))
124
+ continue;
125
+ // A link to a directory is followed here, where it was named.
126
+ const isDir = entry.isDirectory() || (entry.isSymbolicLink() && isDirectory(path.join(dir, entry.name)));
127
+ if (after.length === 0) {
128
+ if (!isDir)
129
+ found.push(join(entry.name));
130
+ }
131
+ else if (isDir) {
132
+ visit(path.join(dir, entry.name), join(entry.name), after);
133
+ }
134
+ }
135
+ };
136
+ visit(path.resolve(cwd, spelledBase || '.'), spelledBase, segments.slice(firstGlob));
137
+ // `**/**` and the like can reach one file twice.
138
+ return [...new Set(found)];
139
+ }
140
+ function statOk(file) {
141
+ try {
142
+ statSync(file);
143
+ return true;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
@@ -0,0 +1,16 @@
1
+ type Chromium = typeof import('@playwright/test')['chromium'];
2
+ interface Playwright {
3
+ chromium: Chromium;
4
+ /** The command that installs the Chromium build it wants. */
5
+ install: string;
6
+ }
7
+ /** The repo's Playwright when it has one, else sm-dev's own. */
8
+ export declare function loadPlaywright(): Promise<Playwright>;
9
+ /**
10
+ * Why Playwright's Chromium at `executable` can't start, when it isn't
11
+ * installed: the Playwright version, the build it wants, the builds
12
+ * that are installed, and `install`, the command that installs it
13
+ * (from `loadPlaywright`). Null when it's there.
14
+ */
15
+ export declare function missingChromium(executable: string, install?: string): string | null;
16
+ export {};
@@ -0,0 +1,86 @@
1
+ // Which Playwright sm-dev drives browsers with, its own Chromium, and
2
+ // what to say when that Chromium isn't installed.
3
+ //
4
+ // - sm-dev uses the Playwright the repo has (`@playwright/test`, an
5
+ // optional peer dependency), so the fixture and the repo's own specs
6
+ // share one copy.
7
+ // - With none, as under `npx sm-dev`, it falls back to the
8
+ // `playwright-core` it depends on itself.
9
+ // - Each Playwright version wants one Chromium build, downloaded apart
10
+ // from the package (`pnpm install` doesn't fetch it) into a folder
11
+ // named for the build: `~/.cache/ms-playwright/chromium-1248/`.
12
+ // Updating Playwright leaves sm-dev wanting a build that isn't there
13
+ // yet, while the old one still is.
14
+ import { existsSync, readdirSync } from 'node:fs';
15
+ import { createRequire } from 'node:module';
16
+ import path from 'node:path';
17
+ import { quoted } from './output-style.js';
18
+ const REPO_INSTALL = 'pnpm exec playwright install chromium';
19
+ let loaded = null;
20
+ function isNotFound(e) {
21
+ const code = e?.code;
22
+ return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
23
+ }
24
+ function packageVersion(name) {
25
+ try {
26
+ return createRequire(import.meta.url)(`${name}/package.json`).version;
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /** The repo's Playwright when it has one, else sm-dev's own. */
33
+ export function loadPlaywright() {
34
+ loaded ??= (async () => {
35
+ try {
36
+ const { chromium } = await import('@playwright/test');
37
+ return { chromium, install: REPO_INSTALL };
38
+ }
39
+ catch (e) {
40
+ if (!isNotFound(e))
41
+ throw e;
42
+ }
43
+ const { chromium } = await import('playwright-core');
44
+ // Pinned to the bundled version: a plain `npx playwright-core`
45
+ // fetches the newest, which wants a different build.
46
+ const version = packageVersion('playwright-core');
47
+ return {
48
+ chromium: chromium,
49
+ install: `npx playwright-core${version ? `@${version}` : ''} install chromium`,
50
+ };
51
+ })();
52
+ return loaded;
53
+ }
54
+ function playwrightVersion() {
55
+ return packageVersion('@playwright/test') ?? packageVersion('playwright-core');
56
+ }
57
+ /**
58
+ * Why Playwright's Chromium at `executable` can't start, when it isn't
59
+ * installed: the Playwright version, the build it wants, the builds
60
+ * that are installed, and `install`, the command that installs it
61
+ * (from `loadPlaywright`). Null when it's there.
62
+ */
63
+ export function missingChromium(executable, install = REPO_INSTALL) {
64
+ if (existsSync(executable))
65
+ return null;
66
+ const version = playwrightVersion();
67
+ const using = version ? `Playwright ${version}` : 'Playwright';
68
+ // The folder named for the build, above the executable.
69
+ let dir = path.dirname(executable);
70
+ while (!/^chromium-\d+$/.test(path.basename(dir)) && path.dirname(dir) !== dir)
71
+ dir = path.dirname(dir);
72
+ const build = /^chromium-(\d+)$/.exec(path.basename(dir))?.[1];
73
+ if (!build)
74
+ return `${using} needs Chromium at ${executable}, which isn't installed. Install it with ${quoted(install)}.`;
75
+ let installed = [];
76
+ try {
77
+ installed = readdirSync(path.dirname(dir)).map((name) => /^chromium-(\d+)$/.exec(name)?.[1])
78
+ // The wanted build's folder can be there without its executable,
79
+ // after an install that was cut off.
80
+ .filter((b) => !!b && b !== build);
81
+ }
82
+ catch { /* No browsers folder yet. */ }
83
+ const have = installed.length ? `(Installed builds: ${installed.join(', ')}.)` : '(No Chromium builds are installed.)';
84
+ return `${using} needs Chromium build ${build}, which isn't installed. ${have} `
85
+ + `Install it with ${quoted(install)}. Run that again after each Playwright update.`;
86
+ }
@@ -0,0 +1,168 @@
1
+ import { type StoredScript, type ScriptMatching, type StartupLog } from '../script-build.js';
2
+ import { type DiagnosticLocation } from '../diagnostic-location.js';
3
+ import { type UrlMatchExplanation } from '../url-match.js';
4
+ import type { PushedFile } from '../external-files.js';
5
+ import { type InjectionWorld } from '../script-world.js';
6
+ export type { InjectionWorld };
7
+ export interface PrepareOptions {
8
+ /** Absolute path of the `.user.js` file. Either this or `text`. */
9
+ file?: string;
10
+ /** The script's source, when it isn't read from disk. */
11
+ text?: string;
12
+ /**
13
+ * Where the script lives, as a URL. Relative `@require`s resolve
14
+ * against it, and only a `file://` script gets local overrides.
15
+ * Defaults to the file's own URL, or a placeholder `file://` URL
16
+ * next to `collectionRoot` when only `text` was given.
17
+ */
18
+ sourceUrl?: string;
19
+ /**
20
+ * The directory the script's repo is rooted at. Searched for local
21
+ * stand-ins of remote `@require` / `@resource` URLs, so a script deep
22
+ * in `sites/` can reach a `lib/` at the top. The script's path is
23
+ * reported relative to it, as the extension would.
24
+ */
25
+ collectionRoot?: string;
26
+ /** Extra directories to search for local stand-ins. */
27
+ localRoots?: string[];
28
+ /** Turn local stand-ins off entirely. Default on. */
29
+ localFiles?: boolean;
30
+ /**
31
+ * Tells two loads of the same file apart, so both can run on one
32
+ * page as two installed copies would: each gets its own script id,
33
+ * its own storage, and its own wrapper. Without it a second load of
34
+ * a path replaces the first.
35
+ */
36
+ copy?: string;
37
+ /**
38
+ * Answers every `fetch` made while preparing (in practice, the
39
+ * `@require` / `@resource` fetches) in place of the network and
40
+ * disk, `file://` included. The corpus validator serves
41
+ * dependencies out of a downloaded tree this way.
42
+ */
43
+ fetch?: (url: string) => Promise<Response>;
44
+ }
45
+ /**
46
+ * Why the syntax check couldn't reach a verdict. The script stays `ok`
47
+ * (the check fails open, see docs/parse-check.md), so this is a warning:
48
+ * - `inconclusive`: V8 threw something other than a SyntaxError.
49
+ * - `errored`: composing the text to check threw.
50
+ */
51
+ export interface ParseCheckNote {
52
+ kind: 'inconclusive' | 'errored';
53
+ message: string;
54
+ }
55
+ export interface PreparedScript {
56
+ /** The row the extension would have stored. */
57
+ script: StoredScript;
58
+ status: 'ok' | 'invalid';
59
+ /** The extension's own message, when `status` is `invalid`. */
60
+ errorMessage?: string;
61
+ errorLocation?: DiagnosticLocation;
62
+ /** `errorMessage` rendered with its location, as the viewer shows it. */
63
+ diagnostic?: string;
64
+ /** Non-fatal parse-time notes: an unknown `@grant`, an ignored `@match`. */
65
+ startupLogs: readonly StartupLog[];
66
+ /** Set when the syntax check couldn't decide; the script stays `ok`. */
67
+ parseCheckNote?: ParseCheckNote;
68
+ /**
69
+ * The run's id for the script, which the host maps to the extension's
70
+ * own once it's pushed (`setPushed`). Unset when invalid.
71
+ */
72
+ id: string | undefined;
73
+ world: InjectionWorld;
74
+ matching: ScriptMatching;
75
+ /** The `@require` URLs, resolved, with where each was read from. */
76
+ requires: {
77
+ url: string;
78
+ localUrl?: string;
79
+ }[];
80
+ }
81
+ export declare function prepareScript(opts: PrepareOptions): Promise<PreparedScript>;
82
+ /**
83
+ * The roots to prepare `file` with, given `collectionRoot` and
84
+ * `localRoots`. A script outside the root it was given (sm-dev run in
85
+ * one repo on another's script, or a shared fixture's root) belongs to
86
+ * the collection whose `script_manifest.json` is nearest above it, as
87
+ * it would be installed. The given root then joins the local roots, so
88
+ * its `lib/` still stands in for the `@require`s that name it.
89
+ */
90
+ export declare function scriptRoots(file: string, collectionRoot: string, localRoots?: string[]): {
91
+ collectionRoot: string;
92
+ localRoots: string[];
93
+ };
94
+ /** What a `ParseCheckNote` means for the person reading it. */
95
+ export declare function parseCheckWarning(note: ParseCheckNote): string;
96
+ /**
97
+ * Every warning from preparing the script, as `<level>: <message>`
98
+ * with the line each is about: the load-time notes the viewer's Log
99
+ * tab shows, then (unless `parseCheck` is false) a syntax check that
100
+ * couldn't decide.
101
+ */
102
+ export declare function warningLines(prepared: PreparedScript, { parseCheck }?: {
103
+ parseCheck?: boolean | undefined;
104
+ }): string[];
105
+ export type MatchExplanation = UrlMatchExplanation;
106
+ /** The reason to give when no header rule names a URL at all. */
107
+ export declare const NO_RULE_COVERS_URL = "No @match or @include covers this URL";
108
+ /** The same, for `match` over several URLs, none of which a rule names. */
109
+ export declare const NO_RULE_COVERS_URLS = "No @match or @include covers these URLs";
110
+ /**
111
+ * Whether the script runs on `url`, and which header rule decided: an
112
+ * exclude wins over everything, then the first `@match`, then the
113
+ * first `@include`. Also every rule that matched or excluded it
114
+ * (`matchedBy`, `excludedBy`). The extension's own matcher, so the
115
+ * answer is the one the extension would give.
116
+ */
117
+ export declare function explainMatch(matching: ScriptMatching, url: string): MatchExplanation;
118
+ /** A script path that names nothing, for `start` and the typed `load`. */
119
+ export declare function noSuchFileMessage(input: string): string;
120
+ /** A script path that names a directory, for `start` and the typed `load`. */
121
+ export declare function isDirectoryMessage(input: string, remedy?: string): string;
122
+ /** Why a script has no default page, as `--all` and `newtab all` report it. */
123
+ export declare const NO_DEFAULT_PAGE = "Its @match names no page to open";
124
+ /**
125
+ * The page a script is opened on when no URL is given: the first one
126
+ * its `@match` implies. Null when none names a page, as with a
127
+ * match-everything pattern.
128
+ */
129
+ export declare function defaultPageUrl(script: PreparedScript): string | null;
130
+ /**
131
+ * A typed URL with `http://` added when it has no scheme, so
132
+ * `example.com/x` works. Only a word that starts with a host reads so:
133
+ * one with a dot, `localhost`, a port (`localhost:8080/x`) or an IPv6
134
+ * address (`[::1]/x`). Any other is a path, made a `file://` URL: one
135
+ * starting `/` or `.`, one naming a file that exists in `cwd`, or one
136
+ * whose first part has no dot (`pages/x.html`), existing or not. `host:port` would
137
+ * otherwise read as a scheme, so only a scheme followed by `//`, or one
138
+ * of the kinds that never have it, counts.
139
+ */
140
+ export declare function withDefaultScheme(url: string, cwd?: string): string;
141
+ /**
142
+ * A path to show: relative to `cwd` when it's inside it, else
143
+ * `outside` (by default the full path). Never one climbing out with
144
+ * `../`, which is harder to read than either.
145
+ */
146
+ export declare function shownPath(file: string, cwd?: string, outside?: string): string;
147
+ /**
148
+ * Where a loaded script is, to show beside its name (`shownPath`).
149
+ * Its `relativePath` stays what identifies it, as in the extension.
150
+ */
151
+ export declare function preparedScriptPath(script: PreparedScript): string;
152
+ /** The script's `@name`, else the path it was loaded from. */
153
+ export declare function preparedScriptName(script: PreparedScript): string;
154
+ /** Whether the script has `@noframes`, which keeps it to the top frame. */
155
+ export declare function preparedNoframes(script: PreparedScript): boolean;
156
+ /**
157
+ * Whether `name` picks out this script: the path it was loaded from,
158
+ * its basename, or its `@name`. How a test or a typed command names
159
+ * one script among several.
160
+ */
161
+ export declare function preparedScriptMatches(script: PreparedScript, name: string): boolean;
162
+ /**
163
+ * The `@require` / `@resource` bodies a run pushes into SourceMonkey with
164
+ * the script's text, by the ref its header declares: those read from
165
+ * disk, a relative ref's file or a local stand-in for a remote URL,
166
+ * which the extension couldn't read itself. Remote ones it fetches.
167
+ */
168
+ export declare function pushedFiles(prepared: PreparedScript): Record<string, PushedFile>;