@jbrowse/capture 4.3.0 → 5.0.0-beta.10
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/README.md +82 -30
- package/esm/args.d.ts +8 -8
- package/esm/args.js +83 -71
- package/esm/bin.js +64 -56
- package/esm/browser.d.ts +13 -1
- package/esm/browser.js +17 -25
- package/esm/capture.d.ts +19 -87
- package/esm/capture.js +54 -237
- package/esm/catalog.d.ts +32 -0
- package/esm/catalog.js +84 -0
- package/esm/hub.d.ts +16 -15
- package/esm/hub.js +36 -29
- package/esm/imagePath.d.ts +1 -0
- package/esm/imagePath.js +8 -0
- package/esm/index.d.ts +12 -9
- package/esm/index.js +8 -7
- package/esm/instanceVersion.d.ts +5 -0
- package/esm/instanceVersion.js +21 -0
- package/esm/poll.d.ts +14 -0
- package/esm/poll.js +30 -0
- package/esm/ready.d.ts +29 -0
- package/esm/ready.js +44 -0
- package/esm/session.d.ts +24 -12
- package/esm/session.js +47 -32
- package/esm/sessionGate.d.ts +38 -105
- package/esm/sessionGate.js +82 -176
- package/esm/sessionOverflow.d.ts +6 -0
- package/esm/sessionOverflow.js +22 -0
- package/esm/url.d.ts +28 -17
- package/esm/url.js +51 -27
- package/esm/version.d.ts +1 -0
- package/esm/version.js +1 -0
- package/esm/waits.d.ts +23 -202
- package/esm/waits.js +77 -404
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -10,7 +10,10 @@ npx @jbrowse/capture --hub hg38 --loc BRCA1 \
|
|
|
10
10
|
|
|
11
11
|
`--hub` names an assembly on [genomes.jbrowse.org](https://genomes.jbrowse.org),
|
|
12
12
|
which hosts a ready-made JBrowse config per UCSC and GenArk genome, and `--loc`
|
|
13
|
-
takes a gene name because those configs ship a text index.
|
|
13
|
+
takes a gene name because those configs ship a text index. `--track` takes a
|
|
14
|
+
trackId, the id without its assembly prefix (`clinvarMain`) or the track's
|
|
15
|
+
display name; capture checks each one and the assembly against the config before
|
|
16
|
+
launching a browser, and a miss fails at once with suggestions.
|
|
14
17
|
|
|
15
18
|
## Knowing when JBrowse has finished rendering
|
|
16
19
|
|
|
@@ -19,10 +22,10 @@ config, builds a session, resolves an assembly, fetches each track, and then
|
|
|
19
22
|
draws to a canvas — and a screenshot taken at any point before the last step is
|
|
20
23
|
a picture of an empty browser that looks like a successful run.
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
display in its loading phase, no unpainted canvas
|
|
24
|
-
a page whose JavaScript has not started
|
|
25
|
-
|
|
25
|
+
Nearly every signal a browser publishes is **negative** — no loading overlay, no
|
|
26
|
+
display in its loading phase, no unpainted canvas — and an absence is equally
|
|
27
|
+
true of a page whose JavaScript has not started. Measured against a released
|
|
28
|
+
build with two remote tracks:
|
|
26
29
|
|
|
27
30
|
- `networkidle2` resolves at ~350ms
|
|
28
31
|
- the session appears at ~880ms
|
|
@@ -32,44 +35,92 @@ a page whose JavaScript has not started yet. Measured against
|
|
|
32
35
|
A wait chain built from those signals alone finishes in under a second and
|
|
33
36
|
reports success.
|
|
34
37
|
|
|
35
|
-
So this package
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
So this package waits on the two **positive** signals instead, and nothing else
|
|
39
|
+
decides the answer:
|
|
40
|
+
|
|
41
|
+
1. **The census.** `AppReadyMarker` publishes what is open as `data-app-views`,
|
|
42
|
+
`data-app-assemblies` and `data-app-tracks`, and the gate reads that one
|
|
43
|
+
element: at least one view, your assembly among them, every trackId you named
|
|
44
|
+
actually open. A trackId or assembly that capture could not check against the
|
|
45
|
+
config before launching (a session spec's, or a config it could not fetch)
|
|
46
|
+
fails here if it never opens. A config, session or plugin the app cannot load
|
|
47
|
+
fails here at once, with the app's own message, which it publishes as
|
|
48
|
+
`data-app-error`.
|
|
49
|
+
2. **`[data-app-phase="ready"]`, held.** The session renders it when no view is
|
|
50
|
+
resolving an assembly and no display is fetching. It has to hold for a beat,
|
|
51
|
+
not merely be true once: a display drops to `ready` in the gap between one
|
|
52
|
+
fetch finishing and the debounced next one starting. The hold also waits out
|
|
53
|
+
a view body still loading its code and a display drawing a morph between two
|
|
54
|
+
layouts (`data-display-animating="true"`), neither of which the marker can
|
|
55
|
+
see.
|
|
56
|
+
|
|
57
|
+
Then one check on what the frame shows, which the marker does not answer. The
|
|
58
|
+
marker is about WORK, so it reads `ready` over a display whose fetch failed or
|
|
59
|
+
that the user canceled. Capture names every display that is unpainted, canceled
|
|
60
|
+
or showing an error banner, and fails.
|
|
61
|
+
|
|
62
|
+
**The instance has to be JBrowse v5 or later**, the first release to publish the
|
|
63
|
+
marker. Capture reads the instance's `version.txt` before launching a browser
|
|
64
|
+
and refuses an older build there, rather than waiting out a timeout on a page
|
|
65
|
+
that will never say it has finished.
|
|
40
66
|
|
|
41
67
|
## Library
|
|
42
68
|
|
|
43
69
|
```js
|
|
44
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
captureJBrowse,
|
|
72
|
+
launchBrowser,
|
|
73
|
+
openJBrowse,
|
|
74
|
+
waitForJBrowseReady,
|
|
75
|
+
} from '@jbrowse/capture'
|
|
45
76
|
|
|
46
77
|
// one call: launch, wait, shoot, close
|
|
47
|
-
const { pending,
|
|
78
|
+
const { pending, unsettled } = await captureJBrowse({
|
|
48
79
|
hub: 'hg38',
|
|
49
80
|
loc: 'BRCA1',
|
|
50
81
|
tracks: ['hg38-ncbiRefSeqCurated'],
|
|
51
82
|
out: 'brca1.png',
|
|
52
83
|
})
|
|
53
84
|
|
|
54
|
-
// or keep the page, to click things
|
|
85
|
+
// or keep the page, to click things, read state back, or shoot one element
|
|
55
86
|
const { browser, page } = await openJBrowse({ hub: 'mm39', loc: 'Sox2' })
|
|
56
87
|
const tracks = await page.evaluate(
|
|
57
88
|
() => window.JBrowseSession.views[0].tracks.length,
|
|
58
89
|
)
|
|
90
|
+
const view = await page.$('[data-testid^="view-container-"]')
|
|
91
|
+
await view.screenshot({ path: 'view-only.png' })
|
|
59
92
|
await browser.close()
|
|
93
|
+
|
|
94
|
+
// or a page of your own that embeds a JBrowse component
|
|
95
|
+
const own = await launchBrowser()
|
|
96
|
+
const ownPage = await own.newPage()
|
|
97
|
+
await ownPage.goto('http://localhost:8000/my-widget.html')
|
|
98
|
+
await waitForJBrowseReady(ownPage)
|
|
99
|
+
await ownPage.screenshot({ path: 'embedded.png' })
|
|
100
|
+
await own.close()
|
|
60
101
|
```
|
|
61
102
|
|
|
103
|
+
The CLI covers the common capture and has no flag for the rest. A crop, a click
|
|
104
|
+
before the shot or a hidden element is a line of Puppeteer on the page
|
|
105
|
+
`openJBrowse` returns, and a view or display setting goes in a `--spec`.
|
|
106
|
+
|
|
62
107
|
Two waits, depending on what you did:
|
|
63
108
|
|
|
64
109
|
- **`waitForJBrowseReady(page)`** is the wait on its own, for a page you
|
|
65
|
-
navigated yourself.
|
|
66
|
-
|
|
67
|
-
|
|
110
|
+
navigated yourself. `waitForFrame(page)` is its second half alone, for a page
|
|
111
|
+
whose session you already gated, after a resize or a click. `waitForSession`,
|
|
112
|
+
the first half, is exported too.
|
|
68
113
|
- **`waitForAppSettled(page)`** is the wait after you CLICK something. A page
|
|
69
114
|
that is loading starts out `loading` and the transition into `ready` is it
|
|
70
115
|
finishing; a page you just clicked is already `ready` and stays that way until
|
|
71
116
|
the click's work registers, so waiting for `ready` there returns on the
|
|
72
|
-
pre-click frame. `waitForAppSettled` requires it to hold
|
|
117
|
+
pre-click frame. `waitForAppSettled` requires it to hold, and throws on a
|
|
118
|
+
build with no marker rather than falling back to a wait that cannot fail.
|
|
119
|
+
|
|
120
|
+
`openJBrowse` opens a JBrowse Web instance. A page of your own that embeds a
|
|
121
|
+
JBrowse component, such as an htmlwidget or a notebook widget, publishes the
|
|
122
|
+
same marker: `launchBrowser`, navigate, then `waitForJBrowseReady(page)`, as the
|
|
123
|
+
last example above does.
|
|
73
124
|
|
|
74
125
|
## Timeouts and unsettled waits
|
|
75
126
|
|
|
@@ -81,11 +132,11 @@ way, which is the vacuous-gate problem again, one step later.
|
|
|
81
132
|
Here every stage reports its outcome, and an unsettled one throws by default,
|
|
82
133
|
naming the gate:
|
|
83
134
|
|
|
84
|
-
```
|
|
85
|
-
gave up waiting after
|
|
86
|
-
never
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
```text
|
|
136
|
+
gave up waiting after 60000ms: the app never held itself ready; display(s)
|
|
137
|
+
never painted: LinearAlignmentsDisplay (volvox_cram-LinearAlignmentsDisplay) is
|
|
138
|
+
loading. Raise the timeout, or pass allowUnsettled (--allowUnsettled) to capture
|
|
139
|
+
the frame as it stands.
|
|
89
140
|
```
|
|
90
141
|
|
|
91
142
|
`allowUnsettled` (`--allowUnsettled`) takes the frame as it stands instead, and
|
|
@@ -97,14 +148,15 @@ Three fields on a successful capture:
|
|
|
97
148
|
|
|
98
149
|
- **`unsettled`** — stages that hit their timeout. Empty unless you asked to
|
|
99
150
|
proceed anyway.
|
|
100
|
-
- **`pending`** — displays
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
151
|
+
- **`pending`** — displays not showing their data when the shutter fired, each
|
|
152
|
+
with its own phase: `loading` is a slow fetch, `error` and `renderError` a
|
|
153
|
+
banner, `canceled` a user's stop, and `ready` a display claiming it finished
|
|
154
|
+
without drawing. Each one lands in `unsettled` too, so a capture returns them
|
|
155
|
+
only under `allowUnsettled`.
|
|
156
|
+
- **`tooLarge`** — displays showing "Requested too much data" instead of their
|
|
157
|
+
features, because the region is wider than the track fetches without being
|
|
158
|
+
asked. The capture still succeeds, since that is the app working as designed,
|
|
159
|
+
and the CLI prints a warning naming them. Narrow the location to draw them.
|
|
108
160
|
|
|
109
161
|
## CLI
|
|
110
162
|
|
package/esm/args.d.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
export interface ParsedArgs {
|
|
2
|
+
command: 'capture' | 'url' | 'list';
|
|
2
3
|
hub?: string;
|
|
3
4
|
config?: string;
|
|
4
5
|
assembly?: string;
|
|
5
6
|
loc?: string;
|
|
6
7
|
tracks: string[];
|
|
8
|
+
spec?: string;
|
|
7
9
|
session?: string;
|
|
8
|
-
sessionName?: string;
|
|
9
10
|
instance?: string;
|
|
10
11
|
out?: string;
|
|
11
12
|
width?: number;
|
|
12
13
|
height?: number;
|
|
13
14
|
scale?: number;
|
|
14
15
|
timeout?: number;
|
|
15
|
-
settle?: number;
|
|
16
16
|
fullPage: boolean;
|
|
17
17
|
headed: boolean;
|
|
18
18
|
verbose: boolean;
|
|
19
19
|
help: boolean;
|
|
20
|
+
version: boolean;
|
|
20
21
|
allowUnsettled: boolean;
|
|
22
|
+
/** `list`'s hub and filter. Empty for the other commands. */
|
|
23
|
+
positionals: string[];
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
23
|
-
* Parse `jb2capture`
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Unknown flags throw rather than being ignored: a mistyped `--tracks` on a tool
|
|
27
|
-
* whose whole job is to produce a plausible-looking image would otherwise be
|
|
28
|
-
* reported by nothing at all.
|
|
26
|
+
* Parse a `jb2capture` command line. Strict: an unknown flag, a flag the
|
|
27
|
+
* command does not use, and a bare word anywhere but after `list` are errors
|
|
28
|
+
* rather than silently ignored.
|
|
29
29
|
*/
|
|
30
30
|
export declare function parseArgs(argv: string[]): ParsedArgs;
|
package/esm/args.js
CHANGED
|
@@ -1,79 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
import { parseArgs as parseNodeArgs } from 'node:util';
|
|
2
|
+
// flags spelled as the library's option names, so a script and a command line
|
|
3
|
+
// say the same thing
|
|
4
|
+
const OPTIONS = {
|
|
5
|
+
hub: { type: 'string' },
|
|
6
|
+
config: { type: 'string' },
|
|
7
|
+
assembly: { type: 'string' },
|
|
8
|
+
loc: { type: 'string' },
|
|
9
|
+
spec: { type: 'string' },
|
|
10
|
+
session: { type: 'string' },
|
|
11
|
+
instance: { type: 'string' },
|
|
12
|
+
out: { type: 'string', short: 'o' },
|
|
13
|
+
track: { type: 'string', multiple: true, default: [] },
|
|
14
|
+
width: { type: 'string' },
|
|
15
|
+
height: { type: 'string' },
|
|
16
|
+
scale: { type: 'string' },
|
|
17
|
+
timeout: { type: 'string' },
|
|
18
|
+
fullPage: { type: 'boolean', default: false },
|
|
19
|
+
headed: { type: 'boolean', default: false },
|
|
20
|
+
verbose: { type: 'boolean', default: false },
|
|
21
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
22
|
+
version: { type: 'boolean', short: 'v', default: false },
|
|
23
|
+
allowUnsettled: { type: 'boolean', default: false },
|
|
24
|
+
};
|
|
25
|
+
const EVERY_COMMAND = ['help', 'version'];
|
|
26
|
+
const SUBCOMMAND_FLAGS = {
|
|
27
|
+
url: new Set([
|
|
28
|
+
'hub',
|
|
29
|
+
'config',
|
|
30
|
+
'assembly',
|
|
31
|
+
'loc',
|
|
32
|
+
'track',
|
|
33
|
+
'spec',
|
|
34
|
+
'session',
|
|
35
|
+
'instance',
|
|
36
|
+
...EVERY_COMMAND,
|
|
37
|
+
]),
|
|
38
|
+
list: new Set(EVERY_COMMAND),
|
|
39
|
+
};
|
|
40
|
+
function finite(name, raw) {
|
|
41
|
+
if (raw === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const n = Number(raw);
|
|
45
|
+
if (!Number.isFinite(n)) {
|
|
46
|
+
throw new Error(`--${name} needs a number, got "${raw}"`);
|
|
47
|
+
}
|
|
48
|
+
return n;
|
|
49
|
+
}
|
|
50
|
+
function positive(name, raw) {
|
|
51
|
+
const n = finite(name, raw);
|
|
52
|
+
if (n !== undefined && n <= 0) {
|
|
53
|
+
throw new Error(`--${name} needs a positive number, got "${raw}"`);
|
|
54
|
+
}
|
|
55
|
+
return n;
|
|
29
56
|
}
|
|
30
57
|
/**
|
|
31
|
-
* Parse `jb2capture`
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* Unknown flags throw rather than being ignored: a mistyped `--tracks` on a tool
|
|
35
|
-
* whose whole job is to produce a plausible-looking image would otherwise be
|
|
36
|
-
* reported by nothing at all.
|
|
58
|
+
* Parse a `jb2capture` command line. Strict: an unknown flag, a flag the
|
|
59
|
+
* command does not use, and a bare word anywhere but after `list` are errors
|
|
60
|
+
* rather than silently ignored.
|
|
37
61
|
*/
|
|
38
62
|
export function parseArgs(argv) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!known(name)) {
|
|
53
|
-
throw new Error(`unknown flag "${arg}"`);
|
|
54
|
-
}
|
|
55
|
-
if (FLAGS.has(name)) {
|
|
56
|
-
out[name] = true;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const value = eq === -1 ? argv[++i] : bare.slice(eq + 1);
|
|
60
|
-
if (value === undefined) {
|
|
61
|
-
throw new Error(`--${name} needs a value`);
|
|
62
|
-
}
|
|
63
|
-
if (REPEATABLE.has(name)) {
|
|
64
|
-
;
|
|
65
|
-
out.tracks.push(value);
|
|
66
|
-
}
|
|
67
|
-
else if (NUMERIC.has(name)) {
|
|
68
|
-
const n = Number(value);
|
|
69
|
-
if (!Number.isFinite(n)) {
|
|
70
|
-
throw new Error(`--${name} needs a number, got "${value}"`);
|
|
63
|
+
const [first, ...afterCommand] = argv;
|
|
64
|
+
const command = first === 'list' || first === 'url' ? first : 'capture';
|
|
65
|
+
const { values, positionals, tokens } = parseNodeArgs({
|
|
66
|
+
args: command === 'capture' ? argv : afterCommand,
|
|
67
|
+
options: OPTIONS,
|
|
68
|
+
allowPositionals: command === 'list',
|
|
69
|
+
tokens: true,
|
|
70
|
+
});
|
|
71
|
+
if (command !== 'capture') {
|
|
72
|
+
for (const token of tokens) {
|
|
73
|
+
if (token.kind === 'option' &&
|
|
74
|
+
!SUBCOMMAND_FLAGS[command].has(token.name)) {
|
|
75
|
+
throw new Error(`${token.rawName} does not apply to \`jb2capture ${command}\``);
|
|
71
76
|
}
|
|
72
|
-
out[name] = n;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
out[name] = value;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
+
const { track, width, height, scale, timeout, ...rest } = values;
|
|
80
|
+
return {
|
|
81
|
+
...rest,
|
|
82
|
+
command,
|
|
83
|
+
// copied, since node hands back the option table's own default array
|
|
84
|
+
tracks: [...track],
|
|
85
|
+
width: positive('width', width),
|
|
86
|
+
height: positive('height', height),
|
|
87
|
+
scale: positive('scale', scale),
|
|
88
|
+
timeout: positive('timeout', timeout),
|
|
89
|
+
positionals,
|
|
90
|
+
};
|
|
79
91
|
}
|
package/esm/bin.js
CHANGED
|
@@ -2,53 +2,51 @@
|
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { parseArgs } from './args.js';
|
|
4
4
|
import { captureJBrowse } from './capture.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { jbrowseUrl } from './url.js';
|
|
5
|
+
import { resolveAgainstConfig } from './catalog.js';
|
|
6
|
+
import { listHubAssemblies, listHubTracks, trackName } from './hub.js';
|
|
7
|
+
import { PUBLIC_INSTANCE, jbrowseUrl } from './url.js';
|
|
8
|
+
import { version } from './version.js';
|
|
8
9
|
const HELP = `jb2capture — screenshot a live JBrowse 2 view, once it has finished drawing
|
|
9
10
|
|
|
10
11
|
USAGE
|
|
11
12
|
jb2capture [flags] --out <file.png> open a view, wait for it, screenshot it
|
|
12
13
|
jb2capture url [flags] print the URL instead of launching a browser
|
|
13
|
-
jb2capture list
|
|
14
|
+
jb2capture list [hub] [filter] list hosted assemblies, or one's trackIds
|
|
14
15
|
|
|
15
16
|
WHAT TO SHOW
|
|
16
17
|
--hub <name> a genomes.jbrowse.org assembly: a UCSC db name (hg38, mm39)
|
|
17
18
|
or a GenArk accession (GCA_.../GCF_...)
|
|
18
19
|
--config <url> a config.json URL, for data that is not hosted there
|
|
19
20
|
--assembly <name> assembly to open (defaults to --hub)
|
|
20
|
-
--loc <locstring>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
--
|
|
25
|
-
--session <json|path> a full session spec, for several views or per-display
|
|
26
|
-
settings. Replaces --assembly/--loc/--track.
|
|
21
|
+
--loc <locstring> chr1:1,000-2,000, several space-separated, or a gene name
|
|
22
|
+
where the config has a text index (hosted ones do)
|
|
23
|
+
--track <trackId> a track to open, by trackId or name; repeat for several
|
|
24
|
+
--spec <json|path|-> a session spec, for several views or per-display settings
|
|
25
|
+
--session <path|-> a session saved with File → Export session
|
|
27
26
|
--instance <url> JBrowse Web deployment to drive
|
|
28
|
-
(default
|
|
29
|
-
--sessionName <name> name the opened session carries
|
|
27
|
+
(default ${PUBLIC_INSTANCE})
|
|
30
28
|
|
|
31
29
|
THE IMAGE
|
|
32
|
-
--out, -o <file>
|
|
30
|
+
--out, -o <file> .png, .jpg or .webp to write (required unless using a
|
|
31
|
+
subcommand)
|
|
33
32
|
--width <px> viewport width (default 1400)
|
|
34
33
|
--height <px> viewport height (default 900)
|
|
35
34
|
--scale <n> device pixel ratio (default 2)
|
|
36
|
-
--fullPage
|
|
35
|
+
--fullPage grow the image until every view fits, not just the
|
|
36
|
+
--height of the viewport
|
|
37
37
|
|
|
38
38
|
WAITING
|
|
39
39
|
--timeout <ms> budget per wait stage (default 60000)
|
|
40
|
-
--
|
|
41
|
-
|
|
40
|
+
--allowUnsettled write the image anyway when a stage times out or a
|
|
41
|
+
display is showing an error or a cancel
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
you asked for, the view resolving, tracks fetching, displays painting, and any
|
|
45
|
-
leftover "Loading…" text. A stage that times out FAILS the run rather than
|
|
46
|
-
quietly handing back a half-drawn frame — raise --timeout, or pass
|
|
47
|
-
--allowUnsettled if you want the frame as it stands.
|
|
43
|
+
Either fails the run rather than writing a frame that is not the picture.
|
|
48
44
|
|
|
49
45
|
OTHER
|
|
50
46
|
--headed run with a visible browser window
|
|
51
|
-
--verbose print browser console output
|
|
47
|
+
--verbose print browser console output and uncaught errors
|
|
48
|
+
(GPU noise filtered)
|
|
49
|
+
--version, -v the package version
|
|
52
50
|
--help, -h this text
|
|
53
51
|
|
|
54
52
|
EXAMPLES
|
|
@@ -63,16 +61,24 @@ EXAMPLES
|
|
|
63
61
|
jb2capture --config https://example.org/config.json --assembly mydata \\
|
|
64
62
|
--loc "chr3:25,325,000-25,361,000 chr10:58,716,500-58,718,500" -o two.png
|
|
65
63
|
`;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
// inline JSON, `-` for stdin, or a path: the forms `jb2export --spec` reads
|
|
65
|
+
function readJson(flag, value) {
|
|
66
|
+
const text = value === '-'
|
|
67
|
+
? readFileSync(0, 'utf8')
|
|
68
|
+
: value.trimStart().startsWith('{')
|
|
69
|
+
? value
|
|
70
|
+
: readFileSync(value, 'utf8');
|
|
70
71
|
const parsed = JSON.parse(text);
|
|
71
72
|
if (typeof parsed !== 'object' || parsed === null) {
|
|
72
|
-
throw new Error(
|
|
73
|
+
throw new Error(`--${flag} must be a JSON object`);
|
|
73
74
|
}
|
|
74
75
|
return parsed;
|
|
75
76
|
}
|
|
77
|
+
// a bare view object is the one-view spec it would be wrapped in
|
|
78
|
+
function readSpec(value) {
|
|
79
|
+
const spec = readJson('spec', value);
|
|
80
|
+
return 'type' in spec && !('views' in spec) ? { views: [spec] } : spec;
|
|
81
|
+
}
|
|
76
82
|
function urlOptions(args) {
|
|
77
83
|
return {
|
|
78
84
|
hub: args.hub,
|
|
@@ -80,16 +86,24 @@ function urlOptions(args) {
|
|
|
80
86
|
assembly: args.assembly,
|
|
81
87
|
loc: args.loc,
|
|
82
88
|
tracks: args.tracks.length ? args.tracks : undefined,
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
spec: args.spec ? readSpec(args.spec) : undefined,
|
|
90
|
+
session: args.session ? readJson('session', args.session) : undefined,
|
|
85
91
|
instance: args.instance,
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
|
-
async function runList(
|
|
89
|
-
const [hub, filter] =
|
|
94
|
+
async function runList(positionals) {
|
|
95
|
+
const [hub, filter] = positionals;
|
|
90
96
|
if (!hub) {
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
const assemblies = await listHubAssemblies();
|
|
98
|
+
const pad = Math.max(...assemblies.map(a => a.name.length));
|
|
99
|
+
for (const a of assemblies) {
|
|
100
|
+
const about = [a.organism, a.description].filter(Boolean).join(' — ');
|
|
101
|
+
console.log(` ${a.name.padEnd(pad)} ${about}`);
|
|
102
|
+
}
|
|
103
|
+
console.log('\nGenArk accessions (GCA_…/GCF_…) work as --hub too; browse them at ' +
|
|
104
|
+
"https://genomes.jbrowse.org. List one assembly's tracks with " +
|
|
105
|
+
'`jb2capture list <name> [filter]`.');
|
|
106
|
+
return;
|
|
93
107
|
}
|
|
94
108
|
const tracks = await listHubTracks(hub, filter);
|
|
95
109
|
if (!tracks.length) {
|
|
@@ -99,30 +113,32 @@ async function runList(rest) {
|
|
|
99
113
|
}
|
|
100
114
|
const pad = Math.min(60, Math.max(...tracks.map(t => t.trackId.length)));
|
|
101
115
|
for (const t of tracks) {
|
|
102
|
-
console.log(` ${t.trackId.padEnd(pad)} ${(t.type ?? '').padEnd(18)} ${t
|
|
116
|
+
console.log(` ${t.trackId.padEnd(pad)} ${(t.type ?? '').padEnd(18)} ${trackName(t)}`);
|
|
103
117
|
}
|
|
104
118
|
}
|
|
105
119
|
async function main() {
|
|
106
120
|
const argv = process.argv.slice(2);
|
|
107
|
-
const
|
|
108
|
-
if (first === 'list') {
|
|
109
|
-
await runList(rest);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const isUrlOnly = first === 'url';
|
|
113
|
-
const args = parseArgs(isUrlOnly ? rest : argv);
|
|
121
|
+
const args = parseArgs(argv);
|
|
114
122
|
if (args.help || argv.length === 0) {
|
|
115
123
|
console.log(HELP);
|
|
116
124
|
return;
|
|
117
125
|
}
|
|
118
|
-
if (
|
|
119
|
-
console.log(
|
|
126
|
+
if (args.version) {
|
|
127
|
+
console.log(version);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (args.command === 'list') {
|
|
131
|
+
await runList(args.positionals);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (args.command === 'url') {
|
|
135
|
+
console.log(jbrowseUrl(await resolveAgainstConfig(urlOptions(args))));
|
|
120
136
|
return;
|
|
121
137
|
}
|
|
122
138
|
if (!args.out) {
|
|
123
139
|
throw new Error('--out <file.png> is required (or use `jb2capture url`)');
|
|
124
140
|
}
|
|
125
|
-
const { url,
|
|
141
|
+
const { url, tooLarge, unsettled } = await captureJBrowse({
|
|
126
142
|
...urlOptions(args),
|
|
127
143
|
out: args.out,
|
|
128
144
|
width: args.width,
|
|
@@ -131,7 +147,6 @@ async function main() {
|
|
|
131
147
|
fullPage: args.fullPage,
|
|
132
148
|
headless: !args.headed,
|
|
133
149
|
timeout: args.timeout,
|
|
134
|
-
settleMs: args.settle,
|
|
135
150
|
allowUnsettled: args.allowUnsettled,
|
|
136
151
|
onConsole: args.verbose
|
|
137
152
|
? text => {
|
|
@@ -141,19 +156,12 @@ async function main() {
|
|
|
141
156
|
});
|
|
142
157
|
console.log(`wrote ${args.out}`);
|
|
143
158
|
console.log(`from ${url}`);
|
|
144
|
-
// Reached only when the wait either succeeded or was explicitly overridden, so
|
|
145
|
-
// these are notes on an image that exists rather than failures. They still get
|
|
146
|
-
// said out loud: "everything painted", "we stopped waiting" and "paint could
|
|
147
|
-
// not be measured here" all look identical in a PNG.
|
|
148
159
|
if (unsettled.length) {
|
|
149
160
|
console.error(`warning: --allowUnsettled: ${unsettled.join('; ')}`);
|
|
150
161
|
}
|
|
151
|
-
if (
|
|
152
|
-
console.error(`warning: ${
|
|
153
|
-
`(${
|
|
154
|
-
}
|
|
155
|
-
else if (!paintContract) {
|
|
156
|
-
console.error(`note: ${PAINT_CONTRACT_NOTE}`);
|
|
162
|
+
if (tooLarge.length) {
|
|
163
|
+
console.error(`warning: ${tooLarge.length} display(s) show "too much data" instead of their features ` +
|
|
164
|
+
`(${tooLarge.map(d => d.name).join(', ')}). Narrow --loc to draw them.`);
|
|
157
165
|
}
|
|
158
166
|
}
|
|
159
167
|
try {
|
package/esm/browser.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export declare const SANDBOX_CHROME_ARGS: string[];
|
|
2
1
|
export declare const BASE_CHROME_ARGS: string[];
|
|
3
2
|
export declare function findChromeExecutable(): string | undefined;
|
|
3
|
+
export interface LaunchOptions {
|
|
4
|
+
headless?: boolean;
|
|
5
|
+
/** Chrome binary. Defaults to $CHROME_PATH, a system Chrome, then Puppeteer's own. */
|
|
6
|
+
executablePath?: string;
|
|
7
|
+
/** Extra Chrome flags, appended to the defaults. */
|
|
8
|
+
args?: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The browser `openJBrowse` drives, for a page it cannot open itself: an
|
|
12
|
+
* embedded view on your own host page, navigated and then waited on with
|
|
13
|
+
* `waitForJBrowseReady`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function launchBrowser({ headless, executablePath, args, }?: LaunchOptions): Promise<import("puppeteer").Browser>;
|
|
4
16
|
export declare function isBrowserConsoleNoise(text: string): boolean;
|