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,439 @@
1
+ // Telling locations apart (http, file, container and collection
2
+ // paths), path helpers, reading a `file://` URL, and Chrome's `file://`
3
+ // directory listings.
4
+ //
5
+ // The part of directories.ts that loading a script's local files
6
+ // needs, in its own module so the development harness can use it
7
+ // without the collection scanners and GitHub provider. directories.ts
8
+ // re-exports it for its other callers.
9
+ import { MANIFEST_FILENAME } from './collection-scan.js';
10
+ // ---------- Collection-kind detection ----------
11
+ // All collection-kind checks key off the raw input string the user
12
+ // typed. `http://` / `https://` selects an HTTP collection; everything
13
+ // else is treated as an absolute filesystem path. Suffix tests look at
14
+ // the URL pathname (sans query/fragment) so e.g. `…/foo.user.js?v=2`
15
+ // still classifies as a single-file URL.
16
+ // The shapes a user may type as a location, as a sentence fragment.
17
+ // Shared because two validators reject on it — `isValidLocationShape`
18
+ // (directories.ts) and the viewer's preview (`isPreviewableInput`) — and their
19
+ // messages had already drifted into two different descriptions of one
20
+ // rule.
21
+ export const LOCATION_SHAPES = 'must be a URL or absolute file path';
22
+ export function isHttpUrl(input) {
23
+ return /^https?:\/\//i.test(input);
24
+ }
25
+ export function isFileUrl(input) {
26
+ return /^file:\/\//i.test(input);
27
+ }
28
+ const CONTAINER_PATH_PREFIX = 'container:';
29
+ // The fixed path of the built-in default container for a scheme.
30
+ export function containerPath(kind) {
31
+ return CONTAINER_PATH_PREFIX + kind;
32
+ }
33
+ // A fresh path for a user-created container.
34
+ export function newContainerPath() {
35
+ return CONTAINER_PATH_PREFIX + crypto.randomUUID();
36
+ }
37
+ // A collection that is a local folder scanned for `*.user.js` files —
38
+ // the kind whose listing is its source of truth, so a file it no
39
+ // longer shows is dropped (see `rebuildCollection`). Everything else
40
+ // (http(s) and GitHub, a manifest, a single-file container) names its
41
+ // scripts some other way.
42
+ export function isLocalDirectoryCollection(collectionPath) {
43
+ return !isHttpUrl(collectionPath)
44
+ && !isManifestCollection(collectionPath)
45
+ && !isContainerCollection(collectionPath);
46
+ }
47
+ export function isContainerCollection(input) {
48
+ return input.startsWith(CONTAINER_PATH_PREFIX);
49
+ }
50
+ // Which scheme this container is the built-in default for — or null
51
+ // for a user-created container (and for anything that isn't a
52
+ // container). NOT a restriction on what the container may hold.
53
+ export function containerKind(input) {
54
+ if (input === 'container:file')
55
+ return 'file';
56
+ if (input === 'container:http')
57
+ return 'http';
58
+ if (input === 'container:github')
59
+ return 'github';
60
+ return null;
61
+ }
62
+ export function collectionKind(path) {
63
+ if (isContainerCollection(path))
64
+ return 'scripts';
65
+ if (isManifestCollection(path))
66
+ return 'manifest';
67
+ return 'directory';
68
+ }
69
+ // Pathname of an http(s) or file:// URL, sans query and fragment.
70
+ // Returns '' if the string doesn't parse as a URL — callers gate on
71
+ // isHttpUrl / isFileUrl first.
72
+ export function urlPathname(input) {
73
+ try {
74
+ return new URL(input).pathname;
75
+ }
76
+ catch {
77
+ return '';
78
+ }
79
+ }
80
+ // Pathname for *any* collection-input form (raw filesystem path,
81
+ // file:// URL, or http(s):// URL). Used by suffix tests that don't care
82
+ // about scheme.
83
+ function locationPathname(input) {
84
+ if (isHttpUrl(input) || isFileUrl(input))
85
+ return urlPathname(input);
86
+ return input;
87
+ }
88
+ // Canonical URL form for a collection input. Raw filesystem paths get
89
+ // percent-encoded and wrapped as file://; URLs pass through. Used as
90
+ // the base for URL.resolution of manifest entries so a local manifest
91
+ // can list relative paths the same way an HTTP one can.
92
+ export function collectionCanonicalUrl(input) {
93
+ if (isHttpUrl(input) || isFileUrl(input))
94
+ return input;
95
+ return pathToFileUrl(input);
96
+ }
97
+ // Inverse of collectionCanonicalUrl for local inputs: a file:// URL
98
+ // turns into the absolute filesystem path it points at; a raw path
99
+ // passes through. Lets the local directory/single-file branches accept
100
+ // the URL form the user typed without each branch having to
101
+ // special-case the parse. Percent-encoded segments (e.g.
102
+ // `my%20scripts`) are decoded so downstream path helpers see the
103
+ // on-disk name verbatim.
104
+ export function locationFsPath(input) {
105
+ if (!isFileUrl(input))
106
+ return input;
107
+ try {
108
+ return decodeURIComponent(new URL(input).pathname);
109
+ }
110
+ catch {
111
+ return input; // unparseable URL → let the caller's I/O fail with a clearer message
112
+ }
113
+ }
114
+ // Windows drive-letter (`file:///C:/…`) and UNC (`file://server/…`)
115
+ // URLs. `locationFsPath` mangles both — it strips only the leading POSIX
116
+ // slash and discards `host` — so the canonicalizer below leaves them
117
+ // as URLs rather than producing a path that points somewhere else.
118
+ // The rest of the module is POSIX-only by design (see `pathToFileUrl`,
119
+ // `parentDir`), so this is containment, not Windows support.
120
+ // Exported because the collections page's `?refresh_file=` validator
121
+ // has to refuse the same two shapes before comparing paths — a mangled
122
+ // path there would silently match the wrong local collection.
123
+ export function isNonPosixFileUrl(input) {
124
+ let u;
125
+ try {
126
+ u = new URL(input);
127
+ }
128
+ catch {
129
+ return false;
130
+ }
131
+ return u.host !== '' || /^\/[A-Za-z]:/.test(u.pathname);
132
+ }
133
+ // One canonical spelling for a collection input, so every entry point
134
+ // agrees on what a given file is called: the collections page's path
135
+ // field, an `addCollection` call, and a captured `file://` navigation
136
+ // all have to produce the same `relativePath`, or one file on disk ends
137
+ // up with two installed rows.
138
+ //
139
+ // The bare filesystem path is the winner because it is what the path
140
+ // field (the oldest and most common route) has always stored. http(s)
141
+ // inputs pass through untouched — there is only one spelling of those.
142
+ export function canonicalCollectionInput(input) {
143
+ if (!isFileUrl(input) || isNonPosixFileUrl(input))
144
+ return input;
145
+ return locationFsPath(input);
146
+ }
147
+ // True for any input pointing at an individual `.user.js` script —
148
+ // http(s):// URL, file:// URL, or absolute local path — based on the
149
+ // pathname suffix. Single-script inputs don't get their own collection
150
+ // row; the install flow puts them into a container collection (by
151
+ // default the one for their scheme — see `containerKindForSingleFileInput`).
152
+ export function isSingleFileInput(input) {
153
+ return locationPathname(input).endsWith('.user.js');
154
+ }
155
+ // True for any manifest collection — http(s):// URL, file:// URL, or an
156
+ // absolute local path — whose pathname's basename is exactly
157
+ // `MANIFEST_FILENAME`. The fixed-filename rule (rather than a generic
158
+ // `.json` suffix) keeps unrelated JSON files from being mistaken for
159
+ // script manifests and gives the convention a single, searchable name.
160
+ export function isManifestCollection(input) {
161
+ const p = locationPathname(input);
162
+ return p === MANIFEST_FILENAME
163
+ || p.endsWith('/' + MANIFEST_FILENAME);
164
+ }
165
+ // ---------- URL / path helpers ----------
166
+ // Build a file:// URL from an absolute filesystem path, percent-encoding
167
+ // each segment so spaces and other reserved characters produce a valid
168
+ // URL. POSIX paths get the conventional `file://` + leading slash.
169
+ export function pathToFileUrl(absolutePath) {
170
+ const trimmed = absolutePath.replace(/\/+$/, '');
171
+ const encoded = trimmed.split('/').map(encodeURIComponent).join('/');
172
+ return encoded.startsWith('/') ? `file://${encoded}` : `file:///${encoded}`;
173
+ }
174
+ export function joinPath(base, child) {
175
+ return base.replace(/\/+$/, '') + '/' + child;
176
+ }
177
+ // Return the parent directory of an absolute POSIX path. The result
178
+ // has no trailing slash; for a top-level path like `/foo` returns `/`.
179
+ export function parentDir(absolutePath) {
180
+ const trimmed = absolutePath.replace(/\/+$/, '');
181
+ const slash = trimmed.lastIndexOf('/');
182
+ if (slash < 0)
183
+ return '';
184
+ if (slash === 0)
185
+ return '/';
186
+ return trimmed.slice(0, slash);
187
+ }
188
+ // Return the leaf name of an absolute POSIX path (the part after the
189
+ // last `/`). Trailing slashes are stripped first.
190
+ export function baseName(absolutePath) {
191
+ const trimmed = absolutePath.replace(/\/+$/, '');
192
+ const slash = trimmed.lastIndexOf('/');
193
+ return slash < 0 ? trimmed : trimmed.slice(slash + 1);
194
+ }
195
+ // ---------- Reading a file:// URL ----------
196
+ // A `file://` URL's text.
197
+ //
198
+ // Why XMLHttpRequest in page contexts: fetch() treats
199
+ // chrome-extension:// → file:// as cross-origin and returns an opaque
200
+ // response (status 0, unreadable body). XHR doesn't apply that
201
+ // restriction. For file:// XHRs, `status` stays 0 even on success
202
+ // (there's no HTTP layer); a missing file fires `onerror`, while a
203
+ // readable file (including a 0-byte one) fires `onload`.
204
+ //
205
+ // Service-worker fallback: MV3 service workers have no XMLHttpRequest
206
+ // at all, so we can't use the XHR path there. fetch() does work from a
207
+ // SW for `file://` URLs as long as the manifest grants `file:///*` in
208
+ // host_permissions (and the user has flipped "Allow access to file
209
+ // URLs") — the cross-origin restriction above is a document-context
210
+ // behavior, not a SW one. Node (the harness) takes this path too. We
211
+ // pick the path at call time by checking for XHR availability.
212
+ export function readFileUrlText(url) {
213
+ if (typeof XMLHttpRequest !== 'undefined')
214
+ return fetchFileUrlTextXhr(url);
215
+ return fetchFileUrlTextFetch(url);
216
+ }
217
+ // `readFileUrlText`, plus what a listing of the file's folder would
218
+ // report for it: its size in bytes and its modified time. One read
219
+ // instead of a listing and a read, for a check that knows the file's
220
+ // path. `fsMtime` is absent when Chrome sent no `Last-Modified`.
221
+ export async function readFileUrlWithStat(url) {
222
+ const { bytes, lastModified } = typeof XMLHttpRequest !== 'undefined'
223
+ ? await fetchFileUrlBytesXhr(url)
224
+ : await fetchFileUrlBytesFetch(url);
225
+ // Decoded as the worker's `response.text()` does: UTF-8, a UTF-8 BOM
226
+ // dropped, bad bytes replaced rather than thrown on. (A page's
227
+ // `responseText` would also detect a UTF-16 BOM; nothing here reads
228
+ // one, and no page calls this today.)
229
+ const text = new TextDecoder().decode(bytes);
230
+ const fsMtime = fsMtimeFromLastModified(lastModified);
231
+ return { text, size: bytes.byteLength, ...(fsMtime !== undefined ? { fsMtime } : {}) };
232
+ }
233
+ // Chrome gives a `file://` response the file's modified time as
234
+ // `Last-Modified`. Its directory listing reports the same time in whole
235
+ // seconds, which `Last-Modified` can't be finer than, so dividing gives
236
+ // the listing's number exactly. That lets a direct read stand in for
237
+ // listing the folder.
238
+ export function fsMtimeFromLastModified(header) {
239
+ if (!header)
240
+ return undefined;
241
+ const ms = Date.parse(header);
242
+ return Number.isFinite(ms) ? Math.floor(ms / 1000) : undefined;
243
+ }
244
+ function fetchFileUrlBytesXhr(url) {
245
+ return new Promise((resolve, reject) => {
246
+ const xhr = new XMLHttpRequest();
247
+ xhr.open('GET', url);
248
+ xhr.responseType = 'arraybuffer';
249
+ xhr.onload = () => resolve({
250
+ bytes: xhr.response ?? new ArrayBuffer(0),
251
+ lastModified: xhr.getResponseHeader('last-modified'),
252
+ });
253
+ xhr.onerror = () => reject(new Error(`Could not read ${url}`));
254
+ xhr.send();
255
+ });
256
+ }
257
+ async function fetchFileUrlBytesFetch(url) {
258
+ const response = await fetchFileUrlResponse(url);
259
+ return { bytes: await response.arrayBuffer(), lastModified: response.headers.get('last-modified') };
260
+ }
261
+ function fetchFileUrlTextXhr(url) {
262
+ return new Promise((resolve, reject) => {
263
+ const xhr = new XMLHttpRequest();
264
+ xhr.open('GET', url);
265
+ xhr.responseType = 'text';
266
+ // For file:// XHRs status is always 0. A readable file (including
267
+ // a 0-byte one) fires `onload`; a missing file fires `onerror`.
268
+ // Resolve with whatever responseText we got — '' is a valid result
269
+ // for an empty file and must not be conflated with "missing".
270
+ xhr.onload = () => resolve(xhr.responseText ?? '');
271
+ xhr.onerror = () => reject(new Error(`Could not read ${url}`));
272
+ xhr.send();
273
+ });
274
+ }
275
+ // Service-worker path: fetch() works for file:// URLs when the SW has
276
+ // the matching host permission. `response.ok` is true for file://
277
+ // fetches that succeed (Chrome synthesises status 200); a 404-style
278
+ // missing file shows up as a fetch rejection or response.ok === false,
279
+ // not a thrown exception with a specific status code, so we have to
280
+ // inspect both.
281
+ async function fetchFileUrlTextFetch(url) {
282
+ // An empty body here is a 0-byte file, not an error — keep it
283
+ // distinct from "missing" so the row renders Size=0 Status=ERROR
284
+ // (no metadata block) rather than getting flagged as gone.
285
+ return await (await fetchFileUrlResponse(url)).text();
286
+ }
287
+ // The worker's `file://` response, once it is known to have read. Shared
288
+ // by the text and byte readers, so a failure reads the same from both.
289
+ async function fetchFileUrlResponse(url) {
290
+ let response;
291
+ try {
292
+ response = await fetch(url);
293
+ }
294
+ catch (err) {
295
+ throw new Error(`Could not read ${url}: ${err instanceof Error ? err.message : String(err)}`);
296
+ }
297
+ if (!response.ok && response.status !== 0) {
298
+ throw new Error(`Could not read ${url}: HTTP ${response.status}`);
299
+ }
300
+ // Opaque responses (response.type === 'opaque') have an unreadable
301
+ // body; surface a distinct error rather than returning empty content,
302
+ // so a manual refresh from a page context can succeed where the SW
303
+ // path fell through.
304
+ if (response.type === 'opaque') {
305
+ throw new Error(`Could not read ${url} in background refresh (file:// fetch is not permitted there). ` +
306
+ `Try Refresh from the script page.`);
307
+ }
308
+ return response;
309
+ }
310
+ // Chrome's file:// directory listing is an HTML page that injects
311
+ // entries via JavaScript calls like:
312
+ // addRow("name", "url", isdir, size, "size_str", date, "date_str")
313
+ // where `isdir` is 0/1 (older Chrome) or true/false. We pull:
314
+ // - the name (first arg) and the isdir flag (third) to classify;
315
+ // - the numeric size (fourth) and date (sixth) so the refresh
316
+ // pipeline can skip refetching files whose size+mtime are
317
+ // unchanged.
318
+ //
319
+ // This is intentionally tolerant: Chrome's exact listing format isn't
320
+ // a stable contract, so the regex matches the common shape and ignores
321
+ // any addRow calls it can't parse. The size/mtime captures are
322
+ // optional — entries without them parse but with size/mtime undefined,
323
+ // at which point the refresh pipeline falls back to always-refetch
324
+ // for those files.
325
+ //
326
+ // An empty result is ambiguous on its own — a listing with no rows, or
327
+ // a page that isn't a listing at all — which is why `listDirectory`
328
+ // checks `isDirectoryListing` first.
329
+ export function parseListing(html) {
330
+ const entries = [];
331
+ const re = /addRow\("((?:\\.|[^"\\])*)"\s*,\s*"(?:\\.|[^"\\])*"\s*,\s*(0|1|true|false)\b(?:\s*,\s*(-?\d+))?(?:\s*,\s*"(?:\\.|[^"\\])*")?(?:\s*,\s*(-?\d+))?/g;
332
+ let m;
333
+ while ((m = re.exec(html)) !== null) {
334
+ let name;
335
+ try {
336
+ // The captured first argument is a JS string literal body —
337
+ // JSON.parse round-trips the standard \" \\ \n \uXXXX escapes.
338
+ name = JSON.parse('"' + m[1] + '"');
339
+ }
340
+ catch {
341
+ continue;
342
+ }
343
+ if (name === '..' || name === '.')
344
+ continue;
345
+ const isDir = m[2] === '1' || m[2] === 'true';
346
+ const entry = { name, isDir };
347
+ if (m[3] !== undefined)
348
+ entry.size = Number(m[3]);
349
+ if (m[4] !== undefined)
350
+ entry.mtime = Number(m[4]);
351
+ entries.push(entry);
352
+ }
353
+ return entries;
354
+ }
355
+ // How many directory listings this JS context has fetched SUCCESSFULLY
356
+ // since the last reset. Scoped and counted narrowly on purpose:
357
+ //
358
+ // - Per-context, not global. This module is imported by the
359
+ // extension pages too, and a check that runs in the page that
360
+ // clicked never touches the worker's copy of this number.
361
+ // - Every listing the context does, not just batched ones — the
362
+ // recursive `scanForUserScripts` walk and collection refreshes land
363
+ // here too, so a test has to reset around the window it cares about.
364
+ // - Cache hits don't count, which is the point: it is the number a
365
+ // test asserts on to show a batch shared one listing rather than
366
+ // doing N.
367
+ //
368
+ // Exposed through the SW test surface (see src/extension/background.ts).
369
+ let listingFetches = 0;
370
+ export function getDirectoryListingFetches() {
371
+ return listingFetches;
372
+ }
373
+ export function resetDirectoryListingFetches() {
374
+ listingFetches = 0;
375
+ }
376
+ // Is this page Chrome's directory listing at all? The listing page
377
+ // opens with a `start("<path>")` call before any rows, and an empty
378
+ // directory still has that. Anything without it — an error page, a
379
+ // path that is now a file, a format Chrome changed under us — is
380
+ // refused rather than read as "no entries", because those two answers
381
+ // have opposite consequences for a refresh: an empty listing drops
382
+ // every script in the collection, a failed one leaves them all alone.
383
+ export function isDirectoryListing(html) {
384
+ return /\bstart\s*\(\s*"/.test(html);
385
+ }
386
+ export const NOT_A_DIRECTORY_LISTING = 'Chrome did not return a directory listing for this folder';
387
+ export async function listDirectory(absolutePath, cache) {
388
+ const hit = cache?.get(absolutePath);
389
+ if (hit)
390
+ return hit;
391
+ const pending = (async () => {
392
+ // Chrome only serves the directory listing for URLs ending with `/`.
393
+ const url = pathToFileUrl(absolutePath) + '/';
394
+ const html = await readFileUrlText(url);
395
+ if (!isDirectoryListing(html)) {
396
+ throw new Error(`Could not read ${url}: ${NOT_A_DIRECTORY_LISTING}`);
397
+ }
398
+ const entries = parseListing(html);
399
+ // Counted after the fetch resolves, so the number means what its
400
+ // name says. A throw leaves it alone.
401
+ listingFetches += 1;
402
+ return entries;
403
+ })();
404
+ if (cache) {
405
+ cache.set(absolutePath, pending);
406
+ // A failed listing must not be remembered. Otherwise one transient
407
+ // read error is inherited by every later sibling in the batch, none
408
+ // of which ever retries — turning a single script's bad luck into
409
+ // the whole page refetching bodies it didn't need to. (Safe either
410
+ // way: `refreshScript` catches, leaving the scan bare, which means
411
+ // always-fetch. The cost is wasted reads, never a missed change.)
412
+ // The identity check keeps a late rejection from evicting a newer
413
+ // entry that has already replaced it.
414
+ void pending.catch(() => {
415
+ if (cache.get(absolutePath) === pending)
416
+ cache.delete(absolutePath);
417
+ });
418
+ }
419
+ return pending;
420
+ }
421
+ // Whether `cache` already holds a listing for this directory — i.e.
422
+ // whether the next `statFromListing` will pay for a fetch. Read by the
423
+ // refresh path's phase timer so a shared listing shows up as a shared
424
+ // listing rather than as a phase that mysteriously got fast.
425
+ export function hasCachedListing(cache, absoluteDir) {
426
+ return cache?.has(absoluteDir) ?? false;
427
+ }
428
+ // Targeted version of listDirectory: list a single directory and return
429
+ // just the entry for `fileName` (or null if absent). Used by the
430
+ // single-file refresh path so it can capture the file's current
431
+ // size/mtime without doing a full recursive scan — keeps the
432
+ // unchanged-file skip honest for the next Refresh All.
433
+ //
434
+ // `cache` is optional and batch-scoped: a navigation's fan-out passes
435
+ // one so N scripts in the same folder list it once between them.
436
+ export async function statFromListing(absoluteDir, fileName, cache) {
437
+ const entries = await listDirectory(absoluteDir, cache);
438
+ return entries.find((e) => e.name === fileName) ?? null;
439
+ }
@@ -0,0 +1,11 @@
1
+ // Type declarations for the runtime module that the build emits
2
+ // directly into dist/generated-wrappers.js from src/wrapper-*.js and
3
+ // src/userscript-prelude.js.
4
+ //
5
+ // No corresponding .ts file exists — keeping this as a .d.ts means
6
+ // tsc doesn't try to compile it, and the build owns the .js output
7
+ // without a stale source-tree copy.
8
+
9
+ export const WRAPPER_PRELUDE: string;
10
+ export const WRAPPER_BRIDGE: string;
11
+ export const WRAPPER_GM_RUNTIME: string;