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.
- package/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- package/skills/SourceMonkey-friction/references/template.md +59 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// What a run (and the spec fixture) knows of its scripts: a record of
|
|
2
|
+
// what they reported and did in its tabs, a copy of their stored
|
|
3
|
+
// values, and their menu commands. The extension runs the scripts;
|
|
4
|
+
// `ExtensionHost` (extension-host.ts) fills this from its events, and
|
|
5
|
+
// the run's commands, waits and output read it.
|
|
6
|
+
//
|
|
7
|
+
// - Every record remembers the page (tab) it came from, kept off the
|
|
8
|
+
// records themselves so they stay plain data a test can compare.
|
|
9
|
+
// - Menu commands belong to a page's current document: a new document
|
|
10
|
+
// registers its own.
|
|
11
|
+
// - A script's values here are a copy, kept up to date from the
|
|
12
|
+
// extension's events, for reads that can't wait on a round trip.
|
|
13
|
+
import { EventEmitter } from 'node:events';
|
|
14
|
+
import { WRAPPER_GM_RUNTIME } from '../generated-wrappers.js';
|
|
15
|
+
import { CHANNEL_KEY_PLACEHOLDER } from '../channel-placeholder.js';
|
|
16
|
+
import { NO_RULE_COVERS_URL, explainMatch, preparedNoframes, preparedScriptName } from './prepare.js';
|
|
17
|
+
import { FrameIds } from './frames.js';
|
|
18
|
+
// The runtime's own stored-value codec, loaded once under Node the way
|
|
19
|
+
// the unit tests do, so a test reading `GM_setValue` output gets the
|
|
20
|
+
// value the script stored rather than its wire envelope.
|
|
21
|
+
let codec = null;
|
|
22
|
+
export function valueCodec() {
|
|
23
|
+
if (codec)
|
|
24
|
+
return codec;
|
|
25
|
+
const window = Object.create(globalThis);
|
|
26
|
+
window.addEventListener = () => { };
|
|
27
|
+
const document = { visibilityState: 'visible' };
|
|
28
|
+
new Function('window', 'document', WRAPPER_GM_RUNTIME)(window, document);
|
|
29
|
+
// The runtime puts itself in the registry its gate hands out; loaded
|
|
30
|
+
// as is, its key is the placeholder.
|
|
31
|
+
const gate = document.__smGate;
|
|
32
|
+
codec = gate(CHANNEL_KEY_PLACEHOLDER).gm.storage;
|
|
33
|
+
return codec;
|
|
34
|
+
}
|
|
35
|
+
// Abstract: the actions on the page go through whatever runs the
|
|
36
|
+
// scripts (`ExtensionHost`).
|
|
37
|
+
export class HarnessHost extends EventEmitter {
|
|
38
|
+
scripts = new Map();
|
|
39
|
+
// Which watched pages hold each script. A script stays known while
|
|
40
|
+
// any page still has it.
|
|
41
|
+
owners = new Map();
|
|
42
|
+
// scriptId -> key -> value in wire form, as the extension stores it.
|
|
43
|
+
values = new Map();
|
|
44
|
+
// Which page each record came from, for telling tabs apart.
|
|
45
|
+
recordPages = new WeakMap();
|
|
46
|
+
// `untilReported`'s waits, checked on each start, skip and error.
|
|
47
|
+
// Not emitter listeners: `reload all` over many tabs would pass
|
|
48
|
+
// Node's listener limit, which prints a warning.
|
|
49
|
+
reportWaiters = new Set();
|
|
50
|
+
reports = {
|
|
51
|
+
started: [], errors: [], skipped: [], manual: [], requests: [], tabs: [], menuCommands: [], notifications: [], downloads: [],
|
|
52
|
+
connectPrompts: [],
|
|
53
|
+
};
|
|
54
|
+
// The manual scripts that match each page's current document, which
|
|
55
|
+
// can be run there.
|
|
56
|
+
manualByPage = new Map();
|
|
57
|
+
// Each menu command's page; dropped when that page loads a new
|
|
58
|
+
// document or goes away.
|
|
59
|
+
menuOwners = new Map();
|
|
60
|
+
/** The run's frame ids (`F3`), across all its tabs. */
|
|
61
|
+
frameIds = new FrameIds();
|
|
62
|
+
/** Register a prepared script as running in `owner`'s tab. */
|
|
63
|
+
addScript(prepared, owner) {
|
|
64
|
+
if (!prepared.id)
|
|
65
|
+
throw new Error(`Cannot host an invalid script: ${prepared.diagnostic}`);
|
|
66
|
+
const id = prepared.id;
|
|
67
|
+
this.scripts.set(id, prepared);
|
|
68
|
+
let set = this.owners.get(id);
|
|
69
|
+
if (!set) {
|
|
70
|
+
set = new Set();
|
|
71
|
+
this.owners.set(id, set);
|
|
72
|
+
}
|
|
73
|
+
set.add(owner);
|
|
74
|
+
if (!this.values.has(id))
|
|
75
|
+
this.values.set(id, new Map());
|
|
76
|
+
}
|
|
77
|
+
/** `owner` no longer has the script; forget it once nobody does. */
|
|
78
|
+
removeScript(scriptId, owner) {
|
|
79
|
+
const set = this.owners.get(scriptId);
|
|
80
|
+
if (!set)
|
|
81
|
+
return;
|
|
82
|
+
set.delete(owner);
|
|
83
|
+
if (set.size > 0)
|
|
84
|
+
return;
|
|
85
|
+
this.owners.delete(scriptId);
|
|
86
|
+
this.scripts.delete(scriptId);
|
|
87
|
+
}
|
|
88
|
+
hasScript(scriptId) {
|
|
89
|
+
return this.scripts.has(scriptId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether a script is running in a page's current top-frame document:
|
|
93
|
+
* its last word from there is a start rather than a skip. For its
|
|
94
|
+
* iframes, see `frameRuns`.
|
|
95
|
+
*/
|
|
96
|
+
isRunning(page, scriptId) {
|
|
97
|
+
const { started, skipped } = this.lastStartAndSkip(page, scriptId);
|
|
98
|
+
return started !== undefined && (!skipped || started.at >= skipped.at);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether the scripts' manager reports their starts, skips and
|
|
102
|
+
* errors. Another manager than SourceMonkey doesn't.
|
|
103
|
+
*/
|
|
104
|
+
get getsReports() {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Of `scriptIds`, the ones with no word from `page` since `since`:
|
|
109
|
+
* not yet started, skipped or thrown there, nor waiting to be run.
|
|
110
|
+
*/
|
|
111
|
+
unreported(page, scriptIds, since) {
|
|
112
|
+
// Nothing to wait for from a manager that never reports.
|
|
113
|
+
if (!this.getsReports)
|
|
114
|
+
return [];
|
|
115
|
+
const { started, skipped, errors, manual } = this.reports;
|
|
116
|
+
const reported = (id) => [started, skipped, errors, manual]
|
|
117
|
+
.some((records) => records.some((r) => r.scriptId === id && r.at >= since && this.recordPages.get(r) === page));
|
|
118
|
+
return scriptIds.filter((id) => !reported(id));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolves once each of `scriptIds` has had its word from `page`
|
|
122
|
+
* since `since` (see `unreported`). Every script the extension runs
|
|
123
|
+
* in a new document gives one, so this is when a load's scripts are
|
|
124
|
+
* done starting. Never rejects. Stops waiting, unresolved, on
|
|
125
|
+
* `signal`, for a page that goes away first.
|
|
126
|
+
*/
|
|
127
|
+
untilReported(page, scriptIds, since, signal) {
|
|
128
|
+
if (this.unreported(page, scriptIds, since).length === 0)
|
|
129
|
+
return Promise.resolve();
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
if (signal?.aborted)
|
|
132
|
+
return;
|
|
133
|
+
const stop = () => {
|
|
134
|
+
this.reportWaiters.delete(check);
|
|
135
|
+
signal?.removeEventListener('abort', stop);
|
|
136
|
+
};
|
|
137
|
+
const check = () => {
|
|
138
|
+
if (this.unreported(page, scriptIds, since).length > 0)
|
|
139
|
+
return;
|
|
140
|
+
stop();
|
|
141
|
+
resolve();
|
|
142
|
+
};
|
|
143
|
+
this.reportWaiters.add(check);
|
|
144
|
+
signal?.addEventListener('abort', stop);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** A script's latest start and latest skip from one page's top frame. */
|
|
148
|
+
lastStartAndSkip(page, scriptId) {
|
|
149
|
+
const mine = (records) => records.filter((r) => r.scriptId === scriptId && !r.frame && this.recordPages.get(r) === page).at(-1);
|
|
150
|
+
return { started: mine(this.reports.started), skipped: mine(this.reports.skipped) };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The scripts running in each iframe document of a page that reported
|
|
154
|
+
* any, by the extension's document id: those whose last word from
|
|
155
|
+
* there is a start. Documents since gone are included; the caller
|
|
156
|
+
* knows which are live (`liveFrameDocuments`).
|
|
157
|
+
*/
|
|
158
|
+
frameRuns(page) {
|
|
159
|
+
const last = new Map();
|
|
160
|
+
const words = [
|
|
161
|
+
...this.reports.started.map((r) => ({ r, running: true })),
|
|
162
|
+
...this.reports.skipped.map((r) => ({ r, running: false })),
|
|
163
|
+
].filter(({ r }) => r.frame && r.documentId && this.recordPages.get(r) === page)
|
|
164
|
+
.sort((a, b) => a.r.at - b.r.at);
|
|
165
|
+
for (const { r, running } of words) {
|
|
166
|
+
let scripts = last.get(r.documentId);
|
|
167
|
+
if (!scripts)
|
|
168
|
+
last.set(r.documentId, scripts = new Map());
|
|
169
|
+
scripts.set(r.scriptId, running);
|
|
170
|
+
}
|
|
171
|
+
return new Map([...last].map(([doc, scripts]) => [doc, new Set([...scripts].filter(([, on]) => on).map(([id]) => id))]));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Whether a script started in any iframe document of a page since
|
|
175
|
+
* `since` (when its top document began), and wasn't skipped there
|
|
176
|
+
* after. Documents since gone count too: for picking the tabs to
|
|
177
|
+
* reload, one too many is better than one missed.
|
|
178
|
+
*/
|
|
179
|
+
ranInFrames(page, scriptId, since) {
|
|
180
|
+
const started = new Set(this.reports.started
|
|
181
|
+
.filter((r) => r.frame && r.documentId && r.scriptId === scriptId && r.at >= since && this.recordPages.get(r) === page)
|
|
182
|
+
.map((r) => r.documentId));
|
|
183
|
+
return [...started].some((doc) => this.frameRuns(page).get(doc)?.has(scriptId));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* A page's live iframe documents, by the extension's id, each with
|
|
187
|
+
* its Playwright frame. Null where the manager can't say; throws when
|
|
188
|
+
* the extension is too old to report on frames.
|
|
189
|
+
*/
|
|
190
|
+
async liveFrameDocuments(_page) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
/** The page a record in `reports` came from, if it is still known. */
|
|
194
|
+
pageOf(record) {
|
|
195
|
+
return this.recordPages.get(record);
|
|
196
|
+
}
|
|
197
|
+
scriptIds() {
|
|
198
|
+
return [...this.scripts.keys()];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The ids of the scripts whose rules match `url`; in an iframe
|
|
202
|
+
* (`inFrame`), leaving out those `@noframes` keeps to the top frame.
|
|
203
|
+
*/
|
|
204
|
+
scriptsFor(url, inFrame) {
|
|
205
|
+
return [...this.scripts].filter(([, s]) => explainMatch(s.matching, url).matches && !(inFrame && preparedNoframes(s))).map(([id]) => id);
|
|
206
|
+
}
|
|
207
|
+
/** A watched page went away. */
|
|
208
|
+
detachPage(page) {
|
|
209
|
+
this.forgetDocumentState(page);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The manual scripts that can be run in `page`'s current document, at
|
|
213
|
+
* its URL now: one the page moved away from in place (pushState) can't.
|
|
214
|
+
*/
|
|
215
|
+
manualScripts(page) {
|
|
216
|
+
const url = page.url();
|
|
217
|
+
return [...this.manualByPage.get(page) ?? []].filter((id) => {
|
|
218
|
+
const script = this.scripts.get(id);
|
|
219
|
+
return script !== undefined && explainMatch(script.matching, url).matches;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
// What belongs to one document of a page: its menu commands, which a
|
|
223
|
+
// new document registers again, and the manual scripts it matches.
|
|
224
|
+
forgetDocumentState(page) {
|
|
225
|
+
this.manualByPage.delete(page);
|
|
226
|
+
for (const [record, owner] of this.menuOwners) {
|
|
227
|
+
if (owner !== page)
|
|
228
|
+
continue;
|
|
229
|
+
this.menuOwners.delete(record);
|
|
230
|
+
const i = this.reports.menuCommands.indexOf(record);
|
|
231
|
+
if (i >= 0)
|
|
232
|
+
this.reports.menuCommands.splice(i, 1);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ---------- Stored values ----------
|
|
236
|
+
/**
|
|
237
|
+
* Values written by the run or the test, as another tab of the script
|
|
238
|
+
* would. Here only the host's copy; `ExtensionHost` writes them into
|
|
239
|
+
* the extension too.
|
|
240
|
+
*/
|
|
241
|
+
async setValues(scriptId, values) {
|
|
242
|
+
const { encodeValue } = valueCodec();
|
|
243
|
+
this.applyWrites(scriptId, Object.entries(values).map(([key, v]) => (v === undefined ? { key, deleted: true } : { key, value: encodeValue(v) })));
|
|
244
|
+
}
|
|
245
|
+
/** Every value a script has stored, decoded, from the host's copy. */
|
|
246
|
+
getValues(scriptId) {
|
|
247
|
+
const { decodeValue } = valueCodec();
|
|
248
|
+
const out = {};
|
|
249
|
+
for (const [k, v] of this.storeFor(scriptId))
|
|
250
|
+
out[k] = decodeValue(v);
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Every value a script has stored, for the run's commands: here the
|
|
255
|
+
* host's copy; `ExtensionHost` asks the extension.
|
|
256
|
+
*/
|
|
257
|
+
async valuesOf(scriptId) {
|
|
258
|
+
return this.getValues(scriptId);
|
|
259
|
+
}
|
|
260
|
+
getValue(scriptId, key) {
|
|
261
|
+
const store = this.storeFor(scriptId);
|
|
262
|
+
return store.has(key) ? valueCodec().decodeValue(store.get(key)) : undefined;
|
|
263
|
+
}
|
|
264
|
+
// Writes into the host's copy; last write wins per key.
|
|
265
|
+
applyWrites(scriptId, writes) {
|
|
266
|
+
const store = this.storeFor(scriptId);
|
|
267
|
+
for (const w of writes) {
|
|
268
|
+
if (typeof w?.key !== 'string')
|
|
269
|
+
continue;
|
|
270
|
+
if (w.deleted)
|
|
271
|
+
store.delete(w.key);
|
|
272
|
+
else
|
|
273
|
+
store.set(w.key, w.value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
storeFor(scriptId) {
|
|
277
|
+
let store = this.values.get(scriptId);
|
|
278
|
+
if (!store) {
|
|
279
|
+
store = new Map();
|
|
280
|
+
this.values.set(scriptId, store);
|
|
281
|
+
}
|
|
282
|
+
return store;
|
|
283
|
+
}
|
|
284
|
+
// ---------- Navigation ----------
|
|
285
|
+
/**
|
|
286
|
+
* A watched page loaded a new document. The extension skips a script
|
|
287
|
+
* whose rules miss the URL silently, so the host records the miss for
|
|
288
|
+
* the developer: which rule kept the script off this URL. `scripts`
|
|
289
|
+
* are the ones the page's tab runs.
|
|
290
|
+
*/
|
|
291
|
+
noteNavigation(page, scripts) {
|
|
292
|
+
this.forgetDocumentState(page);
|
|
293
|
+
const url = page.url();
|
|
294
|
+
for (const script of scripts) {
|
|
295
|
+
const id = script.id ?? '';
|
|
296
|
+
const verdict = explainMatch(script.matching, url);
|
|
297
|
+
if (verdict.matches)
|
|
298
|
+
continue;
|
|
299
|
+
const report = {
|
|
300
|
+
scriptId: id,
|
|
301
|
+
name: preparedScriptName(script),
|
|
302
|
+
url,
|
|
303
|
+
at: Date.now(),
|
|
304
|
+
reason: verdict.rule ?? NO_RULE_COVERS_URL,
|
|
305
|
+
ruleMiss: true,
|
|
306
|
+
};
|
|
307
|
+
this.recordPages.set(report, page);
|
|
308
|
+
this.reports.skipped.push(report);
|
|
309
|
+
this.notify('skip', report);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// ---------- What the scripts reported ----------
|
|
313
|
+
/** A script started in `from`'s current document. */
|
|
314
|
+
recordStart(from, fields) {
|
|
315
|
+
const report = this.baseReport(from, fields);
|
|
316
|
+
this.reports.started.push(report);
|
|
317
|
+
this.notify('start', report);
|
|
318
|
+
}
|
|
319
|
+
/** A script threw in `from`. */
|
|
320
|
+
recordError(from, fields) {
|
|
321
|
+
const report = this.baseReport(from, { ...fields, stack: this.remapStack(fields.stack) });
|
|
322
|
+
this.reports.errors.push(report);
|
|
323
|
+
this.notify('script-error', report);
|
|
324
|
+
}
|
|
325
|
+
/** A script didn't run in `from`, for `reason`. */
|
|
326
|
+
recordSkip(from, fields) {
|
|
327
|
+
const report = this.baseReport(from, fields);
|
|
328
|
+
this.reports.skipped.push(report);
|
|
329
|
+
this.notify('skip', report);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* A manual script can be run in `from`'s document, where it waits to
|
|
333
|
+
* be run. Once per document: the extension says it again on each
|
|
334
|
+
* navigation, a pushState included, and a back/forward restore
|
|
335
|
+
* (which `forgetDocumentState` has cleared it for).
|
|
336
|
+
*/
|
|
337
|
+
recordManual(from, fields) {
|
|
338
|
+
let set = this.manualByPage.get(from);
|
|
339
|
+
if (set?.has(fields.scriptId))
|
|
340
|
+
return;
|
|
341
|
+
if (!set) {
|
|
342
|
+
set = new Set();
|
|
343
|
+
this.manualByPage.set(from, set);
|
|
344
|
+
}
|
|
345
|
+
set.add(fields.scriptId);
|
|
346
|
+
const report = this.baseReport(from, fields);
|
|
347
|
+
this.reports.manual.push(report);
|
|
348
|
+
this.notify('manual', report);
|
|
349
|
+
}
|
|
350
|
+
/** A script in `from` wrote values. */
|
|
351
|
+
recordValueWrites(from, scriptId, writes, frame = {}) {
|
|
352
|
+
if (!this.scripts.has(scriptId))
|
|
353
|
+
return;
|
|
354
|
+
this.applyWrites(scriptId, writes);
|
|
355
|
+
const write = { scriptId, writes, ...frame };
|
|
356
|
+
this.recordPages.set(write, from);
|
|
357
|
+
this.notify('value-write', write);
|
|
358
|
+
}
|
|
359
|
+
/** A script in `from` registered a menu command. */
|
|
360
|
+
registerMenu(from, scriptId, commandId, name, documentId, frame) {
|
|
361
|
+
if (!this.scripts.has(scriptId) || !commandId)
|
|
362
|
+
return;
|
|
363
|
+
const record = {
|
|
364
|
+
scriptId, commandId, name, at: Date.now(), ...(documentId ? { documentId } : {}),
|
|
365
|
+
...(frame ? { frame: true, ...(frame.frameNumber !== undefined ? { frameNumber: frame.frameNumber } : {}) } : {}),
|
|
366
|
+
};
|
|
367
|
+
// Re-registering an id replaces the entry in place, as the worker's
|
|
368
|
+
// registry does. Per document: two frames can use the same id.
|
|
369
|
+
const i = this.reports.menuCommands.findIndex((m) => m.scriptId === scriptId && m.commandId === commandId && m.documentId === documentId && this.menuOwners.get(m) === from);
|
|
370
|
+
if (i >= 0) {
|
|
371
|
+
this.menuOwners.delete(this.reports.menuCommands[i]);
|
|
372
|
+
this.reports.menuCommands[i] = record;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
this.reports.menuCommands.push(record);
|
|
376
|
+
}
|
|
377
|
+
this.menuOwners.set(record, from);
|
|
378
|
+
this.recordPages.set(record, from);
|
|
379
|
+
this.notify('menu-command', record);
|
|
380
|
+
}
|
|
381
|
+
/** A script in `from` removed a menu command. */
|
|
382
|
+
unregisterMenu(from, scriptId, commandId, documentId) {
|
|
383
|
+
const i = this.reports.menuCommands.findIndex((m) => m.scriptId === scriptId && m.commandId === commandId && m.documentId === documentId && this.menuOwners.get(m) === from);
|
|
384
|
+
if (i < 0)
|
|
385
|
+
return;
|
|
386
|
+
this.menuOwners.delete(this.reports.menuCommands[i]);
|
|
387
|
+
this.reports.menuCommands.splice(i, 1);
|
|
388
|
+
}
|
|
389
|
+
// ---------- What a test or command can do ----------
|
|
390
|
+
/**
|
|
391
|
+
* Invoke a registered menu command by its name, or by id, on the page
|
|
392
|
+
* that registered it. The extension runs all the script's
|
|
393
|
+
* registrations of that caption in the tab, as its menu does.
|
|
394
|
+
*/
|
|
395
|
+
async fireMenuCommand(nameOrId, scriptId) {
|
|
396
|
+
const entry = [...this.reports.menuCommands].reverse().find((m) => (scriptId === undefined || m.scriptId === scriptId) && (m.name === nameOrId || m.commandId === nameOrId));
|
|
397
|
+
return entry ? this.fireMenuEntry(entry) : false;
|
|
398
|
+
}
|
|
399
|
+
// ---------- Helpers ----------
|
|
400
|
+
baseReport(from, fields) {
|
|
401
|
+
const report = { ...fields, name: fields.name || this.nameOf(fields.scriptId), at: Date.now() };
|
|
402
|
+
this.recordPages.set(report, from);
|
|
403
|
+
return report;
|
|
404
|
+
}
|
|
405
|
+
nameOf(scriptId) {
|
|
406
|
+
const script = this.scripts.get(scriptId);
|
|
407
|
+
return script ? preparedScriptName(script) : scriptId;
|
|
408
|
+
}
|
|
409
|
+
// Every event goes out through here. A test's listener that throws
|
|
410
|
+
// is the test's own failure: it must not cost the host its records.
|
|
411
|
+
notify(event, ...args) {
|
|
412
|
+
try {
|
|
413
|
+
this.emit(event, ...args);
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Dropped on purpose; see above.
|
|
417
|
+
}
|
|
418
|
+
if (event === 'start' || event === 'skip' || event === 'script-error' || event === 'manual') {
|
|
419
|
+
for (const check of [...this.reportWaiters])
|
|
420
|
+
check();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { prepareScript, explainMatch, type PrepareOptions, type PreparedScript, type MatchExplanation, type InjectionWorld, } from './prepare.js';
|
|
2
|
+
export type { StoredScript, ScriptMatching, StartupLog } from '../script-build.js';
|
|
3
|
+
export type { DiagnosticLocation } from '../diagnostic-location.js';
|
|
4
|
+
export { checkParseNode } from './parse-check-node.js';
|
|
5
|
+
export { HarnessHost, type PageLink, type HostReports, type HostEventMap, type StartReport, type ErrorReport, type SkipReport, type XhrRecord, type TabRecord, type MenuCommandRecord, type NotificationRecord, type DownloadRecord, type RequestInterceptor, } from './host.js';
|
|
6
|
+
export { ExtensionHost } from './extension-host.js';
|
|
7
|
+
export { attachPage, PageAttachment, type InjectOptions } from './page-watch.js';
|
|
8
|
+
export { test, expect, type GmApi, type ReportsApi, type LoadOptions, type HarnessFixtures } from './fixture.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public entry point of the development harness: what another repo
|
|
2
|
+
// gets from `import … from 'sourcemonkey/harness'`. See
|
|
3
|
+
// docs/harness-guide.md for the user-facing description and
|
|
4
|
+
// docs/harness-design.md for the design.
|
|
5
|
+
export { prepareScript, explainMatch, } from './prepare.js';
|
|
6
|
+
export { checkParseNode } from './parse-check-node.js';
|
|
7
|
+
export { HarnessHost, } from './host.js';
|
|
8
|
+
export { ExtensionHost } from './extension-host.js';
|
|
9
|
+
export { attachPage, PageAttachment } from './page-watch.js';
|
|
10
|
+
export { test, expect } from './fixture.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The text of `file`, which is `shown` in messages. Throws an Error
|
|
3
|
+
* saying why it couldn't be read, or that it is empty: nothing, or
|
|
4
|
+
* only whitespace unless `blankOk` (text to type may be just that).
|
|
5
|
+
*/
|
|
6
|
+
export declare function readInputFile(file: string, shown: string, { blankOk }?: {
|
|
7
|
+
blankOk?: boolean | undefined;
|
|
8
|
+
}): string;
|
|
9
|
+
/** The JSON value in `file`, else an Error as `readInputFile` throws. */
|
|
10
|
+
export declare function readJsonFile(file: string, shown: string): unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Why a file couldn't be written, or null for a failure with no
|
|
13
|
+
* common cause. Playwright's own writes (a screenshot) only name the
|
|
14
|
+
* code in their message, hence the match.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeProblem(err: unknown): string | null;
|
|
17
|
+
/** An error's message as a sentence to follow a `: `. */
|
|
18
|
+
export declare function errorSentence(err: unknown): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Files named on a command line for what they hold (`eval --file`,
|
|
2
|
+
// `set --file`, `type --file`, `start --values-file`): read whole, with what went wrong
|
|
3
|
+
// in plain words for an `ERROR:` line.
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
/**
|
|
6
|
+
* The text of `file`, which is `shown` in messages. Throws an Error
|
|
7
|
+
* saying why it couldn't be read, or that it is empty: nothing, or
|
|
8
|
+
* only whitespace unless `blankOk` (text to type may be just that).
|
|
9
|
+
*/
|
|
10
|
+
export function readInputFile(file, shown, { blankOk = false } = {}) {
|
|
11
|
+
let text;
|
|
12
|
+
try {
|
|
13
|
+
text = readFileSync(file, 'utf8');
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
throw new Error(`Couldn't read ${shown}: ${readProblem(err)}`);
|
|
17
|
+
}
|
|
18
|
+
if (blankOk ? !text : !text.trim())
|
|
19
|
+
throw new Error(`The file ${shown} is empty`);
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
/** The JSON value in `file`, else an Error as `readInputFile` throws. */
|
|
23
|
+
export function readJsonFile(file, shown) {
|
|
24
|
+
const text = readInputFile(file, shown);
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(text);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
throw new Error(`The file ${shown} is not JSON: ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Why a file couldn't be read.
|
|
33
|
+
function readProblem(err) {
|
|
34
|
+
if (err.code === 'ENOENT')
|
|
35
|
+
return 'The file does not exist';
|
|
36
|
+
if (err.code === 'EISDIR')
|
|
37
|
+
return 'It\'s a folder';
|
|
38
|
+
if (err.code === 'EACCES')
|
|
39
|
+
return 'Permission was refused';
|
|
40
|
+
return errorSentence(err);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Why a file couldn't be written, or null for a failure with no
|
|
44
|
+
* common cause. Playwright's own writes (a screenshot) only name the
|
|
45
|
+
* code in their message, hence the match.
|
|
46
|
+
*/
|
|
47
|
+
export function writeProblem(err) {
|
|
48
|
+
const code = err.code ?? /\b(ENOENT|EACCES|EISDIR)\b/.exec(err.message)?.[1];
|
|
49
|
+
if (code === 'ENOENT')
|
|
50
|
+
return 'Its folder doesn\'t exist';
|
|
51
|
+
if (code === 'EACCES')
|
|
52
|
+
return 'Permission was refused';
|
|
53
|
+
if (code === 'EISDIR')
|
|
54
|
+
return 'It\'s a folder';
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** An error's message as a sentence to follow a `: `. */
|
|
58
|
+
export function errorSentence(err) {
|
|
59
|
+
const first = err.message.split('\n')[0].replace(/\.$/, '');
|
|
60
|
+
return first.charAt(0).toUpperCase() + first.slice(1);
|
|
61
|
+
}
|