@jbrowse/img 4.1.1 → 4.1.4

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/esm/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/esm/bin.js ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import yargs from 'yargs';
4
+ import { hideBin } from 'yargs/helpers';
5
+ import { convert, parseArgv, renderRegion, setupEnv, standardizeArgv, } from './index.js';
6
+ const trackTypes = [
7
+ 'bam',
8
+ 'cram',
9
+ 'bigwig',
10
+ 'vcfgz',
11
+ 'gffgz',
12
+ 'hic',
13
+ 'bigbed',
14
+ 'bedgz',
15
+ ];
16
+ const knownOptions = new Set([
17
+ ...trackTypes,
18
+ 'fasta',
19
+ 'aliases',
20
+ 'assembly',
21
+ 'config',
22
+ 'session',
23
+ 'loc',
24
+ 'out',
25
+ 'width',
26
+ 'noRasterize',
27
+ 'defaultSession',
28
+ 'tracks',
29
+ 'cytobands',
30
+ 'help',
31
+ 'version',
32
+ ]);
33
+ const yargsInstance = yargs(hideBin(process.argv))
34
+ .scriptName('jb2export')
35
+ .usage('$0 [options]')
36
+ .option('fasta', {
37
+ type: 'string',
38
+ description: 'Path to indexed FASTA file',
39
+ })
40
+ .option('aliases', {
41
+ type: 'string',
42
+ description: 'Path to reference name aliases file',
43
+ })
44
+ .option('assembly', {
45
+ type: 'string',
46
+ description: 'Path to assembly JSON or name in config',
47
+ })
48
+ .option('config', {
49
+ type: 'string',
50
+ description: 'Path to JBrowse config.json',
51
+ })
52
+ .option('session', {
53
+ type: 'string',
54
+ description: 'Path to session JSON',
55
+ })
56
+ .option('loc', {
57
+ type: 'string',
58
+ description: 'Location to render (e.g., chr1:1-1000 or "all")',
59
+ })
60
+ .option('out', {
61
+ type: 'string',
62
+ description: 'Output file path (SVG or PNG)',
63
+ })
64
+ .option('width', {
65
+ type: 'number',
66
+ description: 'Width of output in pixels',
67
+ default: 1500,
68
+ })
69
+ .option('noRasterize', {
70
+ type: 'boolean',
71
+ description: 'Disable rasterization of pileup/coverage',
72
+ default: false,
73
+ })
74
+ .option('defaultSession', {
75
+ type: 'boolean',
76
+ description: 'Use default session from config',
77
+ default: false,
78
+ })
79
+ .example('$0 --fasta ref.fa --bam reads.bam --loc chr1:1-10000 --out out.svg', 'Render BAM alignments')
80
+ .example('$0 --fasta ref.fa --vcfgz variants.vcf.gz --loc chr1:1-50000 --out out.png', 'Render VCF variants')
81
+ .epilogue('Track options: --bam, --cram, --bigwig, --vcfgz, --gffgz, --hic, --bigbed, --bedgz')
82
+ .strict(false)
83
+ .help();
84
+ async function main() {
85
+ const argv = await yargsInstance.argv;
86
+ setupEnv();
87
+ const args = process.argv.slice(2);
88
+ const parsed = parseArgv(args);
89
+ const standardized = standardizeArgv(parsed, trackTypes);
90
+ for (const [key] of parsed) {
91
+ if (!knownOptions.has(key)) {
92
+ console.warn(`Warning: unknown option "--${key}"`);
93
+ }
94
+ }
95
+ const opts = {
96
+ fasta: argv.fasta,
97
+ aliases: argv.aliases,
98
+ assembly: argv.assembly,
99
+ config: argv.config,
100
+ session: argv.session,
101
+ loc: argv.loc,
102
+ width: argv.width,
103
+ noRasterize: argv.noRasterize,
104
+ defaultSession: argv.defaultSession ? 'true' : undefined,
105
+ trackList: standardized.trackList,
106
+ };
107
+ const result = await renderRegion(opts);
108
+ const outFile = argv.out || standardized.out;
109
+ if (!outFile) {
110
+ console.log(result);
111
+ }
112
+ else if (outFile.endsWith('.png')) {
113
+ convert(result, { out: outFile, pngwidth: String(argv.width || 2048) });
114
+ }
115
+ else {
116
+ fs.writeFileSync(outFile, result);
117
+ }
118
+ }
119
+ await main();
package/esm/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { renderRegion } from './renderRegion.tsx';
2
+ export { setupEnv } from './setupEnv.ts';
3
+ export { convert } from './util.ts';
4
+ export { parseArgv, standardizeArgv } from './parseArgv.ts';
5
+ export type { Opts } from './renderRegion.tsx';
package/esm/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { renderRegion } from "./renderRegion.js";
2
+ export { setupEnv } from "./setupEnv.js";
3
+ export { convert } from "./util.js";
4
+ export { parseArgv, standardizeArgv } from "./parseArgv.js";
@@ -0,0 +1,8 @@
1
+ export type Entry = [string, string[]];
2
+ export declare function parseArgv(argv: string[]): Entry[];
3
+ export declare function standardizeArgv(args: Entry[], trackTypes: string[]): {
4
+ [key: string]: unknown;
5
+ trackList: Entry[];
6
+ out?: string;
7
+ pngwidth?: string;
8
+ };
@@ -0,0 +1,29 @@
1
+ export function parseArgv(argv) {
2
+ const map = [];
3
+ while (argv.length) {
4
+ const val = argv[0].slice(2);
5
+ argv = argv.slice(1);
6
+ const next = argv.findIndex(arg => arg.startsWith('-'));
7
+ if (next !== -1) {
8
+ map.push([val, argv.slice(0, next)]);
9
+ argv = argv.slice(next);
10
+ }
11
+ else {
12
+ map.push([val, argv]);
13
+ break;
14
+ }
15
+ }
16
+ return map;
17
+ }
18
+ export function standardizeArgv(args, trackTypes) {
19
+ const result = { trackList: [] };
20
+ for (const arg of args) {
21
+ if (trackTypes.includes(arg[0])) {
22
+ result.trackList.push(arg);
23
+ }
24
+ else {
25
+ result[arg[0]] = arg[1][0] || true;
26
+ }
27
+ }
28
+ return result;
29
+ }
@@ -0,0 +1,34 @@
1
+ import type { Entry } from './parseArgv.ts';
2
+ export interface Opts {
3
+ noRasterize?: boolean;
4
+ loc?: string;
5
+ width?: number;
6
+ session?: string;
7
+ assembly?: string;
8
+ config?: string;
9
+ fasta?: string;
10
+ aliases?: string;
11
+ cytobands?: string;
12
+ defaultSession?: string;
13
+ trackList?: Entry[];
14
+ tracks?: string;
15
+ }
16
+ interface Assembly {
17
+ name: string;
18
+ sequence: Record<string, unknown>;
19
+ refNameAliases?: Record<string, unknown>;
20
+ cytobands?: Record<string, unknown>;
21
+ }
22
+ interface Track {
23
+ trackId: string;
24
+ [key: string]: unknown;
25
+ }
26
+ interface Config {
27
+ assemblies: Assembly[];
28
+ assembly: Assembly;
29
+ tracks: Track[];
30
+ [key: string]: unknown;
31
+ }
32
+ export declare function readData({ assembly: asm, config, session, fasta, aliases, cytobands, defaultSession, tracks, trackList, }: Opts): Config;
33
+ export declare function renderRegion(opts: Opts): Promise<string>;
34
+ export {};
@@ -0,0 +1,377 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { renderToSvg } from '@jbrowse/plugin-linear-genome-view';
4
+ import { createViewState } from '@jbrowse/react-linear-genome-view2';
5
+ import { booleanize } from "./util.js";
6
+ function read(file) {
7
+ let res;
8
+ try {
9
+ res = JSON.parse(fs.readFileSync(file, 'utf8'));
10
+ }
11
+ catch (e) {
12
+ throw new Error(`Failed to parse ${file} as JSON, use --fasta if you mean to pass a FASTA file`, { cause: e });
13
+ }
14
+ return res;
15
+ }
16
+ function makeLocation(file) {
17
+ return file.startsWith('http') ? { uri: file } : { localPath: file };
18
+ }
19
+ function addRelativePaths(config, configPath) {
20
+ if (typeof config === 'object') {
21
+ for (const key of Object.keys(config)) {
22
+ if (typeof config[key] === 'object') {
23
+ addRelativePaths(config[key], configPath);
24
+ }
25
+ else if (key === 'localPath') {
26
+ config.localPath = path.resolve(configPath, config.localPath);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ export function readData({ assembly: asm, config, session, fasta, aliases, cytobands, defaultSession, tracks, trackList = [], }) {
32
+ const assemblyData = asm && fs.existsSync(asm) ? read(asm) : undefined;
33
+ const tracksData = tracks ? read(tracks) : undefined;
34
+ const configData = config ? read(config) : {};
35
+ let sessionData = session
36
+ ? read(session)
37
+ : undefined;
38
+ if (config) {
39
+ addRelativePaths(configData, path.dirname(path.resolve(config)));
40
+ }
41
+ if (sessionData?.session) {
42
+ sessionData = sessionData.session;
43
+ }
44
+ if (sessionData?.views) {
45
+ sessionData.view = sessionData.views[0];
46
+ }
47
+ if (assemblyData) {
48
+ configData.assembly = assemblyData;
49
+ }
50
+ else if (configData.assemblies?.length) {
51
+ configData.assemblies.find(entry => entry.name === asm);
52
+ if (asm) {
53
+ const assembly = configData.assemblies.find(entry => entry.name === asm);
54
+ if (!assembly) {
55
+ throw new Error(`assembly ${asm} not found in config`);
56
+ }
57
+ configData.assembly = assembly;
58
+ }
59
+ else {
60
+ configData.assembly = configData.assemblies[0];
61
+ }
62
+ }
63
+ else if (fasta) {
64
+ const bgzip = fasta.endsWith('gz');
65
+ configData.assembly = {
66
+ name: path.basename(fasta),
67
+ sequence: {
68
+ type: 'ReferenceSequenceTrack',
69
+ trackId: 'refseq',
70
+ adapter: {
71
+ type: bgzip ? 'BgzipFastaAdapter' : 'IndexedFastaAdapter',
72
+ fastaLocation: makeLocation(fasta),
73
+ faiLocation: makeLocation(`${fasta}.fai`),
74
+ gziLocation: bgzip ? makeLocation(`${fasta}.gzi`) : undefined,
75
+ },
76
+ },
77
+ };
78
+ if (aliases) {
79
+ configData.assembly.refNameAliases = {
80
+ adapter: {
81
+ type: 'RefNameAliasAdapter',
82
+ location: makeLocation(aliases),
83
+ },
84
+ };
85
+ }
86
+ if (cytobands) {
87
+ configData.assembly.cytobands = {
88
+ adapter: {
89
+ type: 'CytobandAdapter',
90
+ location: makeLocation(cytobands),
91
+ },
92
+ };
93
+ }
94
+ }
95
+ if (!configData.assembly) {
96
+ throw new Error('no assembly specified, use --fasta to supply an indexed FASTA file (generated with samtools faidx yourfile.fa). see README for alternatives with --assembly and --config');
97
+ }
98
+ if (tracksData) {
99
+ configData.tracks = tracksData;
100
+ }
101
+ else if (!configData.tracks) {
102
+ configData.tracks = [];
103
+ }
104
+ for (const track of trackList) {
105
+ const [type, opts] = track;
106
+ const [file, ...rest] = opts;
107
+ const index = rest.find(r => r.startsWith('index:'))?.replace('index:', '');
108
+ if (!file) {
109
+ throw new Error('no file specified');
110
+ }
111
+ if (type === 'bam') {
112
+ configData.tracks = [
113
+ ...configData.tracks,
114
+ {
115
+ type: 'AlignmentsTrack',
116
+ trackId: path.basename(file),
117
+ name: path.basename(file),
118
+ assemblyNames: [configData.assembly.name],
119
+ adapter: {
120
+ type: 'BamAdapter',
121
+ bamLocation: makeLocation(file),
122
+ index: {
123
+ location: makeLocation(index || `${file}.bai`),
124
+ indexType: index?.endsWith('.csi') ? 'CSI' : 'BAI',
125
+ },
126
+ sequenceAdapter: configData.assembly.sequence.adapter,
127
+ },
128
+ ...(opts.includes('snpcov')
129
+ ? {
130
+ displays: [
131
+ {
132
+ type: 'LinearSNPCoverageDisplay',
133
+ displayId: `${path.basename(file)}-${Math.random()}`,
134
+ },
135
+ ],
136
+ }
137
+ : {}),
138
+ },
139
+ ];
140
+ }
141
+ if (type === 'cram') {
142
+ configData.tracks = [
143
+ ...configData.tracks,
144
+ {
145
+ type: 'AlignmentsTrack',
146
+ trackId: path.basename(file),
147
+ name: path.basename(file),
148
+ assemblyNames: [configData.assembly.name],
149
+ adapter: {
150
+ type: 'CramAdapter',
151
+ cramLocation: makeLocation(file),
152
+ craiLocation: makeLocation(index || `${file}.crai`),
153
+ sequenceAdapter: configData.assembly.sequence.adapter,
154
+ },
155
+ ...(opts.includes('snpcov')
156
+ ? {
157
+ displays: [
158
+ {
159
+ type: 'LinearSNPCoverageDisplay',
160
+ displayId: `${path.basename(file)}-${Math.random()}`,
161
+ },
162
+ ],
163
+ }
164
+ : {}),
165
+ },
166
+ ];
167
+ }
168
+ if (type === 'bigwig') {
169
+ configData.tracks = [
170
+ ...configData.tracks,
171
+ {
172
+ type: 'QuantitativeTrack',
173
+ trackId: path.basename(file),
174
+ name: path.basename(file),
175
+ assemblyNames: [configData.assembly.name],
176
+ adapter: {
177
+ type: 'BigWigAdapter',
178
+ bigWigLocation: makeLocation(file),
179
+ },
180
+ },
181
+ ];
182
+ }
183
+ if (type === 'vcfgz') {
184
+ configData.tracks = [
185
+ ...configData.tracks,
186
+ {
187
+ type: 'VariantTrack',
188
+ trackId: path.basename(file),
189
+ name: path.basename(file),
190
+ assemblyNames: [configData.assembly.name],
191
+ adapter: {
192
+ type: 'VcfTabixAdapter',
193
+ vcfGzLocation: makeLocation(file),
194
+ index: {
195
+ location: makeLocation(index || `${file}.tbi`),
196
+ indexType: index?.endsWith('.csi') ? 'CSI' : 'TBI',
197
+ },
198
+ },
199
+ },
200
+ ];
201
+ }
202
+ if (type === 'gffgz') {
203
+ configData.tracks = [
204
+ ...configData.tracks,
205
+ {
206
+ type: 'FeatureTrack',
207
+ trackId: path.basename(file),
208
+ name: path.basename(file),
209
+ assemblyNames: [configData.assembly.name],
210
+ adapter: {
211
+ type: 'Gff3TabixAdapter',
212
+ gffGzLocation: makeLocation(file),
213
+ index: {
214
+ location: makeLocation(index || `${file}.tbi`),
215
+ indexType: index?.endsWith('.csi') ? 'CSI' : 'TBI',
216
+ },
217
+ },
218
+ },
219
+ ];
220
+ }
221
+ if (type === 'hic') {
222
+ configData.tracks = [
223
+ ...configData.tracks,
224
+ {
225
+ type: 'HicTrack',
226
+ trackId: path.basename(file),
227
+ name: path.basename(file),
228
+ assemblyNames: [configData.assembly.name],
229
+ adapter: {
230
+ type: 'HicAdapter',
231
+ hicLocation: makeLocation(file),
232
+ },
233
+ },
234
+ ];
235
+ }
236
+ if (type === 'bigbed') {
237
+ configData.tracks = [
238
+ ...configData.tracks,
239
+ {
240
+ type: 'FeatureTrack',
241
+ trackId: path.basename(file),
242
+ name: path.basename(file),
243
+ assemblyNames: [configData.assembly.name],
244
+ adapter: {
245
+ type: 'BigBedAdapter',
246
+ bigBedLocation: makeLocation(file),
247
+ },
248
+ },
249
+ ];
250
+ }
251
+ if (type === 'bedgz') {
252
+ configData.tracks = [
253
+ ...configData.tracks,
254
+ {
255
+ type: 'FeatureTrack',
256
+ trackId: path.basename(file),
257
+ name: path.basename(file),
258
+ assemblyNames: [configData.assembly.name],
259
+ adapter: {
260
+ type: 'BedTabixAdapter',
261
+ bedGzLocation: makeLocation(file),
262
+ index: {
263
+ location: makeLocation(index || `${file}.tbi`),
264
+ indexType: index?.endsWith('.csi') ? 'CSI' : 'TBI',
265
+ },
266
+ },
267
+ },
268
+ ];
269
+ }
270
+ }
271
+ if (!defaultSession) {
272
+ configData.defaultSession = undefined;
273
+ }
274
+ if (sessionData) {
275
+ configData.defaultSession = sessionData;
276
+ }
277
+ return configData;
278
+ }
279
+ function process(trackEntry, view, extra = c => c) {
280
+ const [, [track, ...opts]] = trackEntry;
281
+ if (!track) {
282
+ throw new Error('invalid command line args');
283
+ }
284
+ const currentTrack = view.showTrack(extra(track));
285
+ const display = currentTrack.displays[0];
286
+ for (const opt of opts) {
287
+ if (opt.startsWith('height:')) {
288
+ const [, height] = opt.split(':');
289
+ if (height) {
290
+ display.setHeight(+height);
291
+ }
292
+ }
293
+ else if (opt.startsWith('sort:')) {
294
+ const [, type, tag] = opt.split(':');
295
+ display.PileupDisplay.setSortedBy(type, tag);
296
+ }
297
+ else if (opt.startsWith('color:')) {
298
+ const [, type, tag] = opt.split(':');
299
+ if (display.PileupDisplay) {
300
+ display.PileupDisplay.setColorScheme({ type, tag });
301
+ }
302
+ else {
303
+ display.setColor(type);
304
+ }
305
+ }
306
+ else if (opt.startsWith('force:')) {
307
+ const [, force] = opt.split(':');
308
+ if (force) {
309
+ display.setFeatureDensityStatsLimit({ bytes: Number.MAX_VALUE });
310
+ }
311
+ }
312
+ else if (opt.startsWith('autoscale:')) {
313
+ const [, autoscale] = opt.split(':');
314
+ display.setAutoscale(autoscale);
315
+ }
316
+ else if (opt.startsWith('minmax:')) {
317
+ const [, min, max] = opt.split(':');
318
+ if (min) {
319
+ display.setMinScore(+min);
320
+ }
321
+ if (max) {
322
+ display.setMaxScore(+max);
323
+ }
324
+ }
325
+ else if (opt.startsWith('scaletype:')) {
326
+ const [, scaletype] = opt.split(':');
327
+ display.setScaleType(scaletype);
328
+ }
329
+ else if (opt.startsWith('crosshatch:')) {
330
+ const [, val = 'false'] = opt.split(':');
331
+ display.setCrossHatches(booleanize(val));
332
+ }
333
+ else if (opt.startsWith('fill:')) {
334
+ const [, val = 'true'] = opt.split(':');
335
+ display.setFill(booleanize(val));
336
+ }
337
+ else if (opt.startsWith('resolution:')) {
338
+ let [, val = 1] = opt.split(':');
339
+ if (val === 'fine') {
340
+ val = '10';
341
+ }
342
+ else if (val === 'superfine') {
343
+ val = '100';
344
+ }
345
+ display.setResolution(+val);
346
+ }
347
+ }
348
+ }
349
+ export async function renderRegion(opts) {
350
+ const model = createViewState({
351
+ ...readData(opts),
352
+ });
353
+ const { loc, width = 1500, trackList = [], session: sessionParam, defaultSession, } = opts;
354
+ const { session } = model;
355
+ const { view } = session;
356
+ const { assemblyManager } = model;
357
+ view.setWidth(width);
358
+ if (loc) {
359
+ const { name } = assemblyManager.assemblies[0];
360
+ if (loc === 'all') {
361
+ view.showAllRegionsInAssembly(name);
362
+ }
363
+ else {
364
+ await view.navToLocString(loc, name);
365
+ }
366
+ }
367
+ else if (!sessionParam && !defaultSession) {
368
+ console.warn('No loc specified');
369
+ }
370
+ for (const track of trackList) {
371
+ process(track, view, extra => path.basename(extra));
372
+ }
373
+ return renderToSvg(view, {
374
+ rasterizeLayers: !opts.noRasterize,
375
+ ...opts,
376
+ });
377
+ }
@@ -0,0 +1 @@
1
+ export declare function setupEnv(): void;
@@ -0,0 +1,26 @@
1
+ import { TextDecoder, TextEncoder } from 'util';
2
+ import { Image, createCanvas } from 'canvas';
3
+ import { JSDOM } from 'jsdom';
4
+ export function setupEnv() {
5
+ addGlobalCanvasUtils();
6
+ addGlobalTextUtils();
7
+ addGlobalDocument();
8
+ }
9
+ function addGlobalCanvasUtils() {
10
+ global.nodeImage = Image;
11
+ global.nodeCreateCanvas = createCanvas;
12
+ }
13
+ function addGlobalTextUtils() {
14
+ global.TextEncoder = TextEncoder;
15
+ global.TextDecoder = TextDecoder;
16
+ }
17
+ function addGlobalDocument() {
18
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
19
+ url: 'http://localhost/',
20
+ pretendToBeVisual: true,
21
+ resources: 'usable',
22
+ });
23
+ global.window = dom.window;
24
+ global.document = window.document;
25
+ global.localStorage = window.localStorage;
26
+ }
package/esm/util.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function booleanize(str: string): boolean;
2
+ export declare function convert(result: string, args: {
3
+ out: string;
4
+ pngwidth?: string;
5
+ }, spawnArgs?: string[]): void;
package/esm/util.js ADDED
@@ -0,0 +1,23 @@
1
+ import { spawnSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import tmp from 'tmp';
4
+ export function booleanize(str) {
5
+ return str === 'false' ? false : !!str;
6
+ }
7
+ function createTmp() {
8
+ return tmp.fileSync({
9
+ mode: 0o644,
10
+ prefix: 'prefix-',
11
+ postfix: '.svg',
12
+ });
13
+ }
14
+ export function convert(result, args, spawnArgs = []) {
15
+ const { name } = createTmp();
16
+ const { pngwidth = '2048', out } = args;
17
+ fs.writeFileSync(name, result);
18
+ const a = ['-w', pngwidth, name, '-o', out, ...spawnArgs];
19
+ const ls = spawnSync('rsvg-convert', a);
20
+ console.error(`rsvg-convert stderr: ${ls.stderr.toString()}`);
21
+ console.log(`rsvg-convert stdout: ${ls.stdout.toString()}`);
22
+ fs.unlinkSync(name);
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/img",
3
- "version": "4.1.1",
3
+ "version": "4.1.4",
4
4
  "main": "esm/index.js",
5
5
  "types": "esm/index.d.ts",
6
6
  "type": "module",
@@ -13,10 +13,10 @@
13
13
  "author": "JBrowse Team",
14
14
  "license": "Apache-2.0",
15
15
  "engines": {
16
- "node": ">=20"
16
+ "node": ">=23"
17
17
  },
18
18
  "bin": {
19
- "jb2export": "./esm/index.js"
19
+ "jb2export": "./esm/bin.js"
20
20
  },
21
21
  "files": [
22
22
  "esm"
@@ -25,17 +25,16 @@
25
25
  "dependencies": {
26
26
  "@emotion/cache": "^11.14.0",
27
27
  "@emotion/react": "^11.14.0",
28
- "@types/jsdom": "^21.1.7",
28
+ "@types/jsdom": "^27.0.0",
29
29
  "@types/yargs": "^17.0.35",
30
30
  "canvas": "^3.2.1",
31
- "jsdom": "27.0.0",
32
- "node-fetch": "^2.7.0",
33
- "react": "^19.2.3",
34
- "react-dom": "^19.2.3",
31
+ "jsdom": "28.0.0",
32
+ "react": "^19.2.4",
33
+ "react-dom": "^19.2.4",
35
34
  "tmp": "^0.2.5",
36
35
  "yargs": "^18.0.0",
37
- "@jbrowse/plugin-linear-genome-view": "^4.1.1",
38
- "@jbrowse/react-linear-genome-view2": "^4.1.1"
36
+ "@jbrowse/plugin-linear-genome-view": "^4.1.4",
37
+ "@jbrowse/react-linear-genome-view2": "^4.1.4"
39
38
  },
40
39
  "publishConfig": {
41
40
  "access": "public"