@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 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
- Every readiness signal JBrowse publishes is **negative**: no loading overlay, no
23
- display in its loading phase, no unpainted canvas. All of them therefore pass on
24
- a page whose JavaScript has not started yet. Measured against
25
- `jbrowse.org/code/jb2/latest`:
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 puts a **positive gate** in front of them, read off the live MST
36
- session model that jbrowse-web publishes as `window.JBrowseSession`: the session
37
- exists, its views are initialized, and the assembly and trackIds you asked for
38
- are the ones actually open. A config URL that 404s, a trackId the config does
39
- not define, and an assembly name that does not match all fail there, loudly.
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 { captureJBrowse, openJBrowse } from '@jbrowse/capture'
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, paintContract } = await captureJBrowse({
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 and read state back
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. The individual stages (`waitForSession`,
66
- `waitForLoadingComplete`, `waitForDisplaysDone`, `waitForQuiescent`, ...) are
67
- exported too, and each one documents what it can and cannot tell you.
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 2000ms: the loading overlay never cleared (a track fetch
86
- never finished). Raise the timeout if the page is merely slow; if it never
87
- finishes, open the same URL in a browser — this gate has no content to fall
88
- through to.
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 still reporting unpainted when the shutter fired. A
101
- display can return to pending after its stage passed, so this is a separate
102
- question from `unsettled`.
103
- - **`paintContract`** — whether this JBrowse build publishes the per-display
104
- paint attributes at all. It does not on the current released build, which is
105
- what every genomes.jbrowse.org link opens; there, `pending: []` means "cannot
106
- tell", not "all done", and the CLI says so. A page with no tracks open reports
107
- true — there is nothing to measure, which is not the same as being unable to.
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` flags. Split from the binary so the accepted shapes are
24
- * unit-testable without launching a browser.
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
- const NUMERIC = new Set(['width', 'height', 'scale', 'timeout', 'settle']);
2
- const FLAGS = new Set([
3
- 'fullPage',
4
- 'headed',
5
- 'verbose',
6
- 'help',
7
- 'allowUnsettled',
8
- ]);
9
- const REPEATABLE = new Set(['track']);
10
- const STRINGS = new Set([
11
- 'hub',
12
- 'config',
13
- 'assembly',
14
- 'loc',
15
- 'session',
16
- 'sessionName',
17
- 'instance',
18
- 'out',
19
- ]);
20
- // `--fullPage`, not `--full-page`: the flags match the option names in the
21
- // library API one for one, so a script and a command line say the same thing.
22
- // Aliases are the two abbreviations that are hard not to type.
23
- const ALIASES = { o: 'out', h: 'help' };
24
- function known(name) {
25
- return (NUMERIC.has(name) ||
26
- FLAGS.has(name) ||
27
- REPEATABLE.has(name) ||
28
- STRINGS.has(name));
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` flags. Split from the binary so the accepted shapes are
32
- * unit-testable without launching a browser.
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 out = { tracks: [] };
40
- for (const flag of FLAGS) {
41
- out[flag] = false;
42
- }
43
- for (let i = 0; i < argv.length; i++) {
44
- const arg = argv[i];
45
- if (!arg.startsWith('-')) {
46
- throw new Error(`unexpected argument "${arg}"`);
47
- }
48
- const bare = arg.replace(/^--?/, '');
49
- const eq = bare.indexOf('=');
50
- const rawName = eq === -1 ? bare : bare.slice(0, eq);
51
- const name = ALIASES[rawName] ?? rawName;
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
- return out;
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 { listHubTracks } from './hub.js';
6
- import { PAINT_CONTRACT_NOTE } from './sessionGate.js';
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 <hub> [filter] list a hosted assembly's trackIds
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> where to go: chr1:1,000-2,000, or several space-separated
21
- for a discontinuous view. On a config with a text index —
22
- every UCSC assembly on genomes.jbrowse.org has one — a
23
- gene name such as BRCA1 also works.
24
- --track <trackId> a track to open; repeat for several
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 https://jbrowse.org/code/jb2/latest/)
29
- --sessionName <name> name the opened session carries
27
+ (default ${PUBLIC_INSTANCE})
30
28
 
31
29
  THE IMAGE
32
- --out, -o <file> PNG to write (required unless using a subcommand)
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 capture the whole scrollable page, not just the viewport
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
- --settle <ms> extra pause after everything reports drawn (default 0)
41
- --allowUnsettled write the image anyway when a stage times out
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
- Every stage is waited on for you: the session holding the assembly and tracks
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 (GPU noise filtered)
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
- function readSession(value) {
67
- const text = value.trimStart().startsWith('{')
68
- ? value
69
- : readFileSync(value, 'utf8');
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('--session must be a JSON object');
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
- session: args.session ? readSession(args.session) : undefined,
84
- sessionName: args.sessionName,
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(rest) {
89
- const [hub, filter] = rest;
94
+ async function runList(positionals) {
95
+ const [hub, filter] = positionals;
90
96
  if (!hub) {
91
- throw new Error('list needs an assembly, e.g. `jb2capture list hg38`. ' +
92
- 'Browse them at https://genomes.jbrowse.org.');
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.name ?? ''}`);
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 [first, ...rest] = argv;
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 (isUrlOnly) {
119
- console.log(jbrowseUrl(urlOptions(args)));
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, pending, paintContract, unsettled } = await captureJBrowse({
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 (pending.length) {
152
- console.error(`warning: ${pending.length} display(s) had not finished drawing at capture time ` +
153
- `(${pending.join(', ')}). Raise --timeout, or --settle for a slow remote file.`);
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;