@jbrowse/cli 5.0.0-beta.1 → 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.
Files changed (39) hide show
  1. package/README.md +149 -44
  2. package/dist/commands/add-track-utils/adapter-utils.js +47 -31
  3. package/dist/commands/add-track-utils/track-config.js +5 -7
  4. package/dist/commands/add-track.js +31 -9
  5. package/dist/commands/admin-server/index.js +13 -5
  6. package/dist/commands/admin-server/utils.js +8 -6
  7. package/dist/commands/make-density/density-generator.js +180 -0
  8. package/dist/commands/make-density/index.js +139 -0
  9. package/dist/commands/make-pif/index.js +62 -27
  10. package/dist/commands/make-pif/pif-generator.js +98 -132
  11. package/dist/commands/shared/density.js +12 -0
  12. package/dist/commands/text-index/adapter-utils.js +17 -7
  13. package/dist/commands/text-index/aggregate.js +5 -5
  14. package/dist/commands/text-index/command.js +3 -1
  15. package/dist/commands/text-index/config-utils.js +7 -7
  16. package/dist/commands/text-index/indexing-utils.js +21 -49
  17. package/dist/commands/validate/configManifest.generated.js +4064 -1845
  18. package/dist/commands/validate/configSchema.generated.js +20400 -0
  19. package/dist/commands/validate/index.js +21 -5
  20. package/dist/commands/validate/liftConfig.js +34 -0
  21. package/dist/commands/validate/markRules/aggregateFieldName.js +9 -0
  22. package/dist/commands/validate/markRules/autoBin.js +27 -0
  23. package/dist/commands/validate/markRules/colorScale.js +244 -0
  24. package/dist/commands/validate/markRules/cssColorsLevel4.js +162 -0
  25. package/dist/commands/validate/markRules/jexlStrings.js +23 -0
  26. package/dist/commands/validate/markRules/markProblems.js +532 -0
  27. package/dist/commands/validate/markRules/markRuleFacts.js +6 -0
  28. package/dist/commands/validate/markRules/markSpecs.js +96 -0
  29. package/dist/commands/validate/markRules/markVocabulary.js +57 -0
  30. package/dist/commands/validate/markRules/shapeNameList.js +7 -0
  31. package/dist/commands/validate/markRules/stepChannels.js +55 -0
  32. package/dist/commands/validate/schemaValidate.js +425 -0
  33. package/dist/commands/validate/validateConfig.js +304 -259
  34. package/dist/index.js +5 -0
  35. package/dist/version.js +1 -1
  36. package/package.json +6 -10
  37. package/bin/run +0 -4
  38. package/bundle/index.js +0 -1569
  39. package/dist/commands/make-pif/cigar-utils.js +0 -67
package/README.md CHANGED
@@ -13,7 +13,7 @@ or by using a script file.
13
13
 
14
14
  A simple script that does not use @jbrowse/cli at all may just look like this
15
15
 
16
- ```
16
+ ```js
17
17
  const config = JSON.parse(fs.readFileSync('config.json', 'utf8'))
18
18
  // do something with config.tracks, config.assemblies, etc.
19
19
  fs.writeFileSync('config.json', JSON.stringify(config, null, 2))
@@ -43,7 +43,7 @@ npx @jbrowse/cli create myfolder
43
43
  It is likely preferable in most cases to install the tools globally with
44
44
  `npm install @jbrowse/cli -g` however
45
45
 
46
- ```
46
+ ```text
47
47
 
48
48
  JBrowse CLI
49
49
 
@@ -59,6 +59,7 @@ COMMANDS
59
59
  admin-server Start up a small admin server for JBrowse configuration
60
60
  upgrade Upgrades JBrowse 2 to latest version
61
61
  make-pif Creates pairwise indexed PAF (PIF), with bgzip and tabix
62
+ make-density Counts feature starts per bin into a bigWig density sidecar, with bedGraphToBigWig
62
63
  sort-gff Sort a GFF/GTF for tabix: sort -k1,1 -k4,4n, header kept on top
63
64
  sort-bed Sort a BED for tabix: sort -k1,1 -k2,2n, header kept on top
64
65
  add-connection Add a connection to a JBrowse 2 configuration
@@ -77,7 +78,7 @@ Use "jbrowse <command> --help" for more information about a command.
77
78
 
78
79
  ## jbrowse create
79
80
 
80
- ```
81
+ ```text
81
82
  Downloads and installs the latest JBrowse 2 release
82
83
 
83
84
  Usage: jbrowse create [localPath] [options]
@@ -122,7 +123,7 @@ $ jbrowse create --listVersions
122
123
 
123
124
  ## jbrowse add-assembly
124
125
 
125
- ```
126
+ ```text
126
127
  Add an assembly to a JBrowse 2 configuration
127
128
 
128
129
  Usage: jbrowse add-assembly <sequence> [options]
@@ -241,7 +242,7 @@ $ jbrowse add-assembly GRCh38.fa --load copy --config '{"sequence":{"formatAbout
241
242
 
242
243
  ## jbrowse add-track
243
244
 
244
- ```
245
+ ```text
245
246
  Add a track to a JBrowse 2 configuration
246
247
 
247
248
  Usage: jbrowse add-track <track> [options]
@@ -263,9 +264,9 @@ Options:
263
264
  -a, --assemblyNames Assembly name or names for track as comma separated
264
265
  string. For pairwise synteny tracks the order is
265
266
  query,target (reverse of minimap2/nucmer argument
266
- order); for all-vs-all adapters
267
- (AllVsAllPAFAdapter/AllVsAllIndexedPAFAdapter) list
268
- every assembly the file covers, in any order
267
+ order); for the multi-genome PAF adapters
268
+ (MultiGenomePAFAdapter/MultiGenomeIndexedPAFAdapter)
269
+ list every assembly the file covers, in any order
269
270
 
270
271
  --category Optional comma separated string of categories to
271
272
  group tracks
@@ -279,7 +280,7 @@ Options:
279
280
  displayDefaults
280
281
 
281
282
  --displayDefaults Inline JSON merged into the track displayDefaults
282
- (labels, mouseover, jexlFilters, etc.)
283
+ (labels, mouseover, filter, etc.)
283
284
 
284
285
  --multiwig Build a MultiQuantitativeTrack from several BigWigs
285
286
  (in place of the positional track arg): a
@@ -310,6 +311,11 @@ Options:
310
311
 
311
312
  --bed2 Used only for mcscan anchors/simpleAnchors types
312
313
 
314
+ --density Path or URL of a features-per-bin bigWig (jbrowse
315
+ make-density) to attach as the adapter's
316
+ densityAdapter. A local <file>.density.bw beside
317
+ the track file is attached without this flag
318
+
313
319
  Notes:
314
320
 
315
321
  --load controls how the data file is placed relative to config.json: copy, move,
@@ -329,7 +335,7 @@ text-index.
329
335
  JSON. Wrap the value in single quotes and use double quotes inside a jexl
330
336
  callback so nothing needs escaping, e.g. --color
331
337
  'jexl:feature.strand==1?"blue":"red"'. --displayDefaults takes inline JSON for
332
- any other appearance setting (labels, mouseover, jexlFilters).
338
+ any other appearance setting (labels, mouseover, filter).
333
339
 
334
340
  --multiwig bundles several BigWigs into one MultiQuantitativeTrack, in place of
335
341
  the positional track argument: pass a comma-separated list of BigWig files/URLs,
@@ -337,11 +343,18 @@ or a .json file with an array of BigWig locations or subadapter objects (each
337
343
  carrying its own name/color/group). With --load, local list entries are copied
338
344
  like any other track file.
339
345
 
346
+ --density attaches a features-per-bin bigWig (jbrowse make-density) as the
347
+ adapter's densityAdapter, the band a display draws where the region is too large
348
+ to fetch features. A local <file>.density.bw beside the track file is attached
349
+ without the flag; a URL cannot be probed, so a remote sidecar needs it. The
350
+ adapters carrying the slot are BamAdapter, CramAdapter, HtsgetBamAdapter,
351
+ Gff3TabixAdapter, GtfTabixAdapter, BedTabixAdapter, BigBedAdapter,
352
+ VcfTabixAdapter and SplitVcfTabixAdapter.
353
+
340
354
  For pairwise synteny adapters (PAF/Delta/Chain) --assemblyNames is query,target
341
- — the reverse of the minimap2/nucmer input order. For the all-vs-all adapters
342
- (AllVsAllPAFAdapter, AllVsAllIndexedPAFAdapter) it is instead the full list of
343
- assemblies the file covers, in any order, since one all-vs-all file backs every
344
- pair.
355
+ — the reverse of the minimap2/nucmer input order. For the multi-genome PAF
356
+ adapters (MultiGenomePAFAdapter, MultiGenomeIndexedPAFAdapter) it is instead the
357
+ full list of assemblies the file covers, in any order.
345
358
 
346
359
  Examples:
347
360
 
@@ -371,12 +384,15 @@ $ jbrowse add-track --multiwig a.bw,b.bw,c.bw --load copy --name "Coverage"
371
384
 
372
385
  # ...or from a sources.json carrying per-row name/color for each BigWig
373
386
  $ jbrowse add-track --multiwig sources.json --name "CATlas ATAC"
387
+
388
+ # attach a remote density sidecar (a local genes.gff3.density.bw needs no flag)
389
+ $ jbrowse add-track https://mywebsite.com/genes.gff3.gz --density https://mywebsite.com/genes.gff3.density.bw
374
390
  ```
375
391
 
376
392
 
377
393
  ## jbrowse validate
378
394
 
379
- ```
395
+ ```text
380
396
  Check a JBrowse configuration for errors, including the ones JBrowse itself
381
397
  accepts silently
382
398
 
@@ -397,19 +413,35 @@ nothing. That is what this command is mainly for.
397
413
 
398
414
  Two levels are reported:
399
415
 
400
- error JBrowse accepts it and silently does the wrong thing — an unknown
401
- slot, a track naming an assembly the config never defines, a
416
+ error JBrowse silently does the wrong thing, or refuses to load it — an
417
+ unknown slot, a key a defaultSession view or display does not
418
+ declare, a track naming an assembly the config never defines, a
402
419
  defaultSession naming a trackId that does not exist, a duplicate
403
420
  trackId. Exits 1.
404
421
 
405
422
  warning JBrowse will complain by itself on load, or handles it — a type name
406
423
  the core plugins do not register (expected if one of your plugins
407
- registers it), or a legacy key a migration rewrites. Never fails
408
- the run.
424
+ registers it), or a legacy key a migration rewrites, such as a view
425
+ nesting its settings under "init". Never fails the run.
426
+
427
+ A LinearMarkDisplay loads whenever its keys and value types are right and says
428
+ what it cannot draw in a corner notice rather than refusing the track. This
429
+ command runs the display's own rule list over every "marks" list in the file: an
430
+ error where a mark draws nothing, never draws or a step cannot run, and a
431
+ warning where a slot waits unread or the marks draw in an arrangement the author
432
+ may not have meant. With --json each finding carries the stable "rule" id of the
433
+ rule that made it.
409
434
 
410
435
  Types registered by plugins are not known to this command, so they come through
411
436
  as warnings rather than errors.
412
437
 
438
+ The same checks are published as a JSON Schema at
439
+ https://jbrowse.org/jb2/schema/v5/config.json: a "$schema" line naming it at the
440
+ top of a config.json gives an editor completion and validation for every slot.
441
+ This command runs that schema first, then the cross-references a schema cannot
442
+ express — a trackId a session names that no track defines, an assembly a track
443
+ names that none defines.
444
+
413
445
  Examples:
414
446
 
415
447
  # check the config.json in the current directory
@@ -426,7 +458,7 @@ $ jbrowse validate config.json --json
426
458
 
427
459
  ## jbrowse text-index
428
460
 
429
- ```
461
+ ```text
430
462
  Make a text-indexing file for any given track(s).
431
463
 
432
464
  Usage: jbrowse text-index [options]
@@ -499,7 +531,8 @@ GtfAdapter, GtfTabixAdapter, VcfAdapter, VcfTabixAdapter) are indexed; tracks
499
531
  with other adapter types are skipped automatically.
500
532
 
501
533
  GTF has no Name/ID attributes, so the default --attributes also match their GTF
502
- spellings (gene_name, transcript_name, gene_id, transcript_id).
534
+ spellings (gene_name, transcript_name, gene_id, transcript_id). A GFF3 row with
535
+ no Name, as in GENCODE, is named by its own gene_name or transcript_name.
503
536
 
504
537
  --exclude names types not to index; --include names the only types to index.
505
538
  Reach for --include when the file draws from a vocabulary you do not control: an
@@ -536,7 +569,7 @@ $ jbrowse text-index --file myfile.gff3.gz --file myfile.vcfgz --out indexes
536
569
 
537
570
  ## jbrowse admin-server
538
571
 
539
- ```
572
+ ```text
540
573
  Start up a small admin server for JBrowse configuration
541
574
 
542
575
  Usage: jbrowse admin-server [options]
@@ -547,6 +580,9 @@ Options:
547
580
  -p, --port Specified port to start the server on (default:
548
581
  9090)
549
582
 
583
+ --host Interface to bind to (default: 127.0.0.1). Use
584
+ 0.0.0.0 to accept connections from other machines
585
+
550
586
  --root Path to the root of the JB2 installation
551
587
 
552
588
  --bodySizeLimit Size limit of the update message (default: 25mb)
@@ -554,9 +590,10 @@ Options:
554
590
  Notes:
555
591
 
556
592
  The admin-server lets a browser session write changes back to config.json on
557
- disk, authorized by a one-time key printed in the startup URL. It is meant for
558
- local configuration only: run it on a trusted machine and do not expose the port
559
- to untrusted networks or the public internet.
593
+ disk, authorized by a one-time key printed in the startup URL. It binds to
594
+ 127.0.0.1 so only this machine can reach it. The key gates the config routes,
595
+ but the rest of the JBrowse directory is served unauthenticated, so widen --host
596
+ only on a trusted network.
560
597
 
561
598
  Examples:
562
599
 
@@ -571,12 +608,15 @@ $ jbrowse admin-server --root /path/to/jb2/
571
608
 
572
609
  # raise the body size limit for very large config updates
573
610
  $ jbrowse admin-server --bodySizeLimit 100mb
611
+
612
+ # accept connections from other machines on the network
613
+ $ jbrowse admin-server --host 0.0.0.0
574
614
  ```
575
615
 
576
616
 
577
617
  ## jbrowse upgrade
578
618
 
579
- ```
619
+ ```text
580
620
  Upgrades JBrowse 2 to latest version
581
621
 
582
622
  Usage: jbrowse upgrade [localPath] [options]
@@ -624,7 +664,7 @@ $ jbrowse upgrade --nightly
624
664
 
625
665
  ## jbrowse make-pif
626
666
 
627
- ```
667
+ ```text
628
668
  creates pairwise indexed PAF (PIF), with bgzip and tabix
629
669
 
630
670
  Usage: jbrowse make-pif <file> [options]
@@ -637,16 +677,22 @@ Options:
637
677
 
638
678
  --csi Create a CSI index for the PIF file instead of TBI
639
679
 
640
- --coarse Minimum insertion/deletion length (bp) at which a
641
- coarse-tier row is split into multiple pieces so
642
- each row stays tight — 0 emits an unsplit coarse
643
- tier. Defaults to 10000. The no-CIGAR coarse tier
644
- (prefix T/Q) is emitted alongside the per-row CIGAR
645
- fine tier by default so whole-genome synteny views
646
- can auto-switch to it; pass --no-coarse to omit it.
680
+ --coarse Accuracy bound (bp) of the coarse tier. A coarse
681
+ row replaces its CIGAR with a coarse CIGAR (cr:Z:)
682
+ that keeps every indel longer than half this length
683
+ and folds the rest into runs, each run staying
684
+ within this many bp of the real alignment. Defaults
685
+ to 10000. The coarse tier (prefix T/Q) is emitted
686
+ alongside the per-row CIGAR fine tier by default so
687
+ whole-genome synteny views can auto-switch to it;
688
+ pass --no-coarse to omit it.
689
+
690
+ --no-coarse Do not emit the coarse tier; write only the per-row
691
+ CIGAR fine tier.
647
692
 
648
- --no-coarse Do not emit the coarse no-CIGAR tier; write only
649
- the per-row CIGAR fine tier.
693
+ --threads Compression threads for bgzip. Defaults to 4. Raise
694
+ it on a machine with cores to spare, or set 1 to
695
+ leave the rest of the machine alone.
650
696
 
651
697
  Notes:
652
698
 
@@ -666,17 +712,76 @@ $ jbrowse make-pif input.paf --out output.pif.gz
666
712
  # use a CSI index for assemblies with chromosomes longer than ~512 Mb
667
713
  $ jbrowse make-pif input.paf --csi
668
714
 
669
- # emit an unsplit coarse tier (alongside the fine tier)
670
- $ jbrowse make-pif input.paf --coarse 0
715
+ # a looser coarse tier: runs within 50kb of the alignment, indels over 25kb kept
716
+ $ jbrowse make-pif input.paf --coarse 50000
671
717
 
672
718
  # emit only the per-row CIGAR fine tier, skipping the coarse tier
673
719
  $ jbrowse make-pif input.paf --no-coarse
720
+
721
+ # give bgzip more (or fewer) compression threads than the default 4
722
+ $ jbrowse make-pif input.paf --threads 8
674
723
  ```
675
724
 
676
725
 
677
- ## jbrowse sort-gff
726
+ ## jbrowse make-density
727
+
728
+ ```text
729
+ Counts feature starts per bin into a bigWig, the density sidecar a track draws
730
+ where the region is too large to fetch its features
731
+
732
+ Usage: jbrowse make-density <file> [options]
733
+
734
+ Options:
735
+ -h, --help Show help
736
+
737
+ --bin Width of each bin, in bp [default: 1000]
738
+
739
+ --chrom-sizes Two-column reference name and length table for the
740
+ assembly the file is on
741
+
742
+ --assembly FASTA of the assembly the file is on; its .fai
743
+ supplies the reference lengths, in place of
744
+ --chrom-sizes
745
+
746
+ --out Where to write the bigWig. Defaults to
747
+ <file>.density.bw beside the input, which is where
748
+ add-track looks for it
749
+
750
+ Notes:
751
+
752
+ The sidecar is attached to a track through its adapter's densityAdapter slot,
753
+ which BamAdapter, CramAdapter, HtsgetBamAdapter, Gff3TabixAdapter,
754
+ GtfTabixAdapter, BedTabixAdapter, BigBedAdapter, VcfTabixAdapter and
755
+ SplitVcfTabixAdapter declare. add-track attaches it on its own when the default
756
+ <file>.density.bw sits beside the track file, so building it before adding the
757
+ track is all it takes; --density attaches one by path or URL.
758
+
759
+ GFF3 counts only top-level features — a line whose attributes carry no Parent= —
760
+ so a gene is one count rather than one per exon. GTF, BED and VCF have no parent
761
+ link to follow, so every record counts.
762
+
763
+ BAM and CRAM are out of scope: read depth is not feature density, and the tools
764
+ that compute it already write bigWig. Point add-track --density at what they
765
+ produce.
766
+
767
+ Requires bedGraphToBigWig (UCSC) on the PATH.
768
+
769
+ Examples:
770
+
771
+ # writes genes.gff3.density.bw beside the input, in 1kb bins
772
+ $ jbrowse make-density genes.gff3.gz --chrom-sizes hg38.chrom.sizes
773
+
774
+ # take the reference lengths from the assembly FASTA index instead
775
+ $ jbrowse make-density variants.vcf.gz --assembly hg38.fa
678
776
 
777
+ # coarser bins, and a name of your own
778
+ $ jbrowse make-density genes.gff3.gz --assembly hg38.fa --bin 10000 --out genes.10kb.bw
679
779
  ```
780
+
781
+
782
+ ## jbrowse sort-gff
783
+
784
+ ```text
680
785
  Sort a GFF or GTF for tabix — the two share a refname/start column layout. It is
681
786
  `sort -k1,1 -k4,4n` with LC_ALL=C and a tab separator, plus every line starting
682
787
  with # kept at the top rather than sorted into the data. Takes a file, or the
@@ -704,7 +809,7 @@ $ tabix -p gff sorted.gtf.gz
704
809
 
705
810
  ## jbrowse sort-bed
706
811
 
707
- ```
812
+ ```text
708
813
  Sort a BED file for tabix. It is `sort -k1,1 -k2,2n` with LC_ALL=C and a tab
709
814
  separator, plus every line starting with # kept at the top rather than sorted
710
815
  into the data. Takes a file, or the same data on stdin
@@ -726,7 +831,7 @@ $ tabix sorted.bed.gz
726
831
 
727
832
  ## jbrowse add-connection
728
833
 
729
- ```
834
+ ```text
730
835
  Add a connection to a JBrowse 2 configuration
731
836
 
732
837
  Usage: jbrowse add-connection <connectionUrlOrPath> [options]
@@ -782,7 +887,7 @@ $ jbrowse add-connection https://mysite.com/path/to/hub.txt --connectionId newId
782
887
 
783
888
  ## jbrowse add-track-json
784
889
 
785
- ```
890
+ ```text
786
891
  Add a track configuration directly from a JSON hunk to the JBrowse 2
787
892
  configuration
788
893
 
@@ -825,7 +930,7 @@ $ jbrowse add-track-json track.json --out /path/to/jb2/
825
930
 
826
931
  ## jbrowse remove-track
827
932
 
828
- ```
933
+ ```text
829
934
  Remove a track configuration from a JBrowse 2 configuration. Be aware that this
830
935
  can cause crashes in saved sessions that refer to this track!
831
936
 
@@ -854,7 +959,7 @@ $ jbrowse remove-track my_track_id --target /path/to/jb2/config.json
854
959
 
855
960
  ## jbrowse set-default-session
856
961
 
857
- ```
962
+ ```text
858
963
  Set a default session with views and tracks
859
964
 
860
965
  Usage: jbrowse set-default-session [options]
@@ -1,5 +1,8 @@
1
1
  import fs from 'node:fs';
2
- import { adapterTypesToTrackTypeMap, formats, matchFormat, resolveIndexType, trackTypeForAdapter, } from '@jbrowse/add-track-core';
2
+ import { adapterTypesToTrackTypeMap, formats, matchFormat, resolveIndexType, sidecarCandidateNames, trackTypeForAdapter, } from '@jbrowse/add-track-core';
3
+ import { isURL } from '../../types/common.js';
4
+ import { densitySidecarPath } from '../shared/density.js';
5
+ import { configManifest } from '../validate/configManifest.generated.js';
3
6
  function hasLocField(spec) {
4
7
  return 'locField' in spec;
5
8
  }
@@ -9,24 +12,6 @@ for (const { spec } of formats) {
9
12
  adapterTypeToSpec[spec.adapterType] = spec;
10
13
  }
11
14
  }
12
- // What else the sidecar with this suffix can be named. `.csi` is htslib's
13
- // alternative for a BAI or a TBI — required for a reference over 512 Mb, and
14
- // written on request at any size. `stripped` is the form Picard and GATK write:
15
- // `reads.bai` beside `reads.bam`, where samtools writes `reads.bam.bai`.
16
- //
17
- // Per-suffix because the alternatives are: a `.crai` has no CSI form, and
18
- // stripping the extension off `calls.vcf.gz` would name `calls.vcf.tbi`, which
19
- // nothing writes. A `.fai`/`.gzi` has neither and so is absent here.
20
- //
21
- // This is `indexCandidateNames` in `@jbrowse/core/util/indexCandidates`, which
22
- // the add-track widget and jbrowse-img share and this CLI cannot import — it
23
- // carries no `@jbrowse/core` dependency, so `npm i -g @jbrowse/cli` stays a CLI
24
- // rather than a copy of the app. Change one, change the other.
25
- const ALTERNATE_SPELLINGS = {
26
- '.bai': { csi: true, stripped: true },
27
- '.crai': { stripped: true },
28
- '.tbi': { csi: true },
29
- };
30
15
  /**
31
16
  * The sidecar actually sitting beside `location`, or the conventional name when
32
17
  * none of the spellings is there — so a missing index still reports the file
@@ -35,22 +20,53 @@ const ALTERNATE_SPELLINGS = {
35
20
  * Probed with `existsSync`, which is false for every candidate of a URL and so
36
21
  * leaves a remote track with exactly the conventional guess it had before this
37
22
  * existed. A remote `.csi` still wants `--indexFile`.
38
- *
39
- * The guard on the stripped spelling is load-bearing: `replace` hands back the
40
- * subject unchanged when the pattern does not match, so `--adapterType
41
- * BamAdapter` on a file named with no extension at all would offer the data
42
- * file as its own index — and that file certainly exists.
43
23
  */
44
24
  export function siblingSidecar(location, suffix) {
45
- const { csi, stripped } = ALTERNATE_SPELLINGS[suffix] ?? {};
46
- const strippedName = location.replace(/\.[^./\\]+$/, suffix);
47
- const candidates = [
48
- `${location}${suffix}`,
49
- ...(csi ? [`${location}.csi`] : []),
50
- ...(stripped && strippedName !== location ? [strippedName] : []),
51
- ];
25
+ const candidates = sidecarCandidateNames(location, suffix);
52
26
  return candidates.find(c => fs.existsSync(c)) ?? candidates[0];
53
27
  }
28
+ // The adapters that declare a `densityAdapter` slot, read off the generated
29
+ // manifest `jbrowse validate` uses. The hand-written list missed `GWASAdapter`,
30
+ // which inherits the slot through `bedTabixConfigSchema` instead of spreading
31
+ // `densityAdapterConfigSchemaFields`, so `--density` on a Pan-UKBB file was
32
+ // refused by a message that listed nine adapters and not the tenth.
33
+ const densityAdapterTypes = new Set(Object.entries(configManifest.adapters)
34
+ .filter(([, entry]) => entry.slots.some(s => s.name === 'densityAdapter'))
35
+ .map(([name]) => name)
36
+ .sort());
37
+ /**
38
+ * The adapter with its density sidecar attached, and the local file to load
39
+ * beside the config when there is one.
40
+ *
41
+ * An explicit `--density` is taken as given, since a URL cannot be probed;
42
+ * otherwise the conventional `<file>.density.bw` counts only when it is
43
+ * actually there, the way `siblingSidecar` probes an index.
44
+ */
45
+ export function withDensityAdapter({ adapter, location, density, makeLocation, }) {
46
+ const supported = densityAdapterTypes.has(adapter.type);
47
+ if (density !== undefined && !supported) {
48
+ throw new Error(`--density has no slot on ${adapter.type}. The adapters carrying a density sidecar are: ${[...densityAdapterTypes].join(', ')}`);
49
+ }
50
+ const probed = location !== undefined && fs.existsSync(densitySidecarPath(location))
51
+ ? densitySidecarPath(location)
52
+ : undefined;
53
+ if (probed !== undefined && !supported) {
54
+ console.warn(`Warning: ${probed} sits beside the track file but ${adapter.type} has no densityAdapter slot, so it is not attached. The adapters carrying one are indexed: bgzip and tabix the file, then add it.`);
55
+ }
56
+ const file = density === undefined ? (supported ? probed : undefined) : density;
57
+ return file === undefined
58
+ ? { adapter }
59
+ : {
60
+ adapter: {
61
+ ...adapter,
62
+ densityAdapter: {
63
+ type: 'BigWigAdapter',
64
+ bigWigLocation: makeLocation(file),
65
+ },
66
+ },
67
+ file: isURL(file) ? undefined : file,
68
+ };
69
+ }
54
70
  // builds the adapter object and the list of source files for a spec in one
55
71
  // place, so the config it writes and the files add-track copies can never drift
56
72
  function buildFromSpec(spec, { location, index, bed1, bed2, makeLocation }) {
@@ -29,10 +29,8 @@ export function buildTrackConfig({ trackType, trackId, name, assemblyNames, cate
29
29
  // nothing was supplied, so the track omits displayDefaults entirely.
30
30
  //
31
31
  // Takes `trackType` so it can refuse a key none of that track's displays
32
- // declares. `--color` on an alignments track is the reachable case — that
33
- // display declares `colorBy`, expandTrackConfigShorthand drops the unmatched
34
- // key with a console warning, and the command otherwise wrote the dead setting
35
- // and exited 0.
32
+ // declares: expandTrackConfigShorthand drops an unmatched key with a console
33
+ // warning, and the command otherwise wrote the dead setting and exited 0.
36
34
  export function mergeDisplayDefaults({ configObj, color, height, displayDefaults, trackType, }) {
37
35
  const fromJson = displayDefaults
38
36
  ? parseJsonFlag(displayDefaults, '--displayDefaults')
@@ -56,9 +54,9 @@ export function mergeDisplayDefaults({ configObj, color, height, displayDefaults
56
54
  // An unknown track type accepts everything: it is a plugin's, and this manifest
57
55
  // is only the core plugins'.
58
56
  //
59
- // The message names the display when the track offers one — which most do, and
60
- // AlignmentsTrack is the case that matters here, since `--color` is the
61
- // reachable mistake and LinearAlignmentsDisplay is the only place to go look.
57
+ // The message names the display where the track offers exactly one, and the
58
+ // track otherwise: the keys checked are the union over its displays, so
59
+ // naming one of several would send the reader to look in the wrong place.
62
60
  function checkDisplayDefaultKeys(merged, trackType) {
63
61
  const { displayTypes, keys } = displayDefaultsForTrackType(trackType);
64
62
  if (keys.length === 0) {
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { parseArgs } from 'node:util';
3
3
  import { printHelp, readConfigFile, resolveConfigPath } from '../utils.js';
4
- import { guessTrack, guessTrackType, makeLocationProtocol, } from './add-track-utils/adapter-utils.js';
4
+ import { guessTrack, guessTrackType, makeLocationProtocol, withDensityAdapter, } from './add-track-utils/adapter-utils.js';
5
5
  import { loadFiles } from './add-track-utils/file-operations.js';
6
6
  import { buildMultiWiggle } from './add-track-utils/multiwig.js';
7
7
  import { addSyntenyAssemblyNames, buildTrackConfig, mapLocationForFiles, mergeDisplayDefaults, } from './add-track-utils/track-config.js';
@@ -39,7 +39,7 @@ export async function run(args) {
39
39
  assemblyNames: {
40
40
  type: 'string',
41
41
  short: 'a',
42
- description: 'Assembly name or names for track as comma separated string. For pairwise synteny tracks the order is query,target (reverse of minimap2/nucmer argument order); for all-vs-all adapters (AllVsAllPAFAdapter/AllVsAllIndexedPAFAdapter) list every assembly the file covers, in any order',
42
+ description: 'Assembly name or names for track as comma separated string. For pairwise synteny tracks the order is query,target (reverse of minimap2/nucmer argument order); for the multi-genome PAF adapters (MultiGenomePAFAdapter/MultiGenomeIndexedPAFAdapter) list every assembly the file covers, in any order',
43
43
  },
44
44
  category: {
45
45
  type: 'string',
@@ -59,7 +59,7 @@ export async function run(args) {
59
59
  },
60
60
  displayDefaults: {
61
61
  type: 'string',
62
- description: 'Inline JSON merged into the track displayDefaults (labels, mouseover, jexlFilters, etc.)',
62
+ description: 'Inline JSON merged into the track displayDefaults (labels, mouseover, filter, etc.)',
63
63
  },
64
64
  multiwig: {
65
65
  type: 'string',
@@ -104,6 +104,10 @@ export async function run(args) {
104
104
  type: 'string',
105
105
  description: 'Used only for mcscan anchors/simpleAnchors types',
106
106
  },
107
+ density: {
108
+ type: 'string',
109
+ description: "Path or URL of a features-per-bin bigWig (jbrowse make-density) to attach as the adapter's densityAdapter. A local <file>.density.bw beside the track file is attached without this flag",
110
+ },
107
111
  };
108
112
  const { values: flags, positionals } = parseArgs({
109
113
  args,
@@ -128,17 +132,23 @@ export async function run(args) {
128
132
  'a jexl callback so nothing needs escaping, e.g. ' +
129
133
  '--color \'jexl:feature.strand==1?"blue":"red"\'. --displayDefaults takes ' +
130
134
  'inline JSON for any other appearance setting (labels, mouseover, ' +
131
- 'jexlFilters).\n\n' +
135
+ 'filter).\n\n' +
132
136
  '--multiwig bundles several BigWigs into one MultiQuantitativeTrack, in ' +
133
137
  'place of the positional track argument: pass a comma-separated list of ' +
134
138
  'BigWig files/URLs, or a .json file with an array of BigWig locations or ' +
135
139
  'subadapter objects (each carrying its own name/color/group). With --load, ' +
136
140
  'local list entries are copied like any other track file.\n\n' +
141
+ '--density attaches a features-per-bin bigWig (jbrowse make-density) as the ' +
142
+ "adapter's densityAdapter, the band a display draws where the region is too " +
143
+ 'large to fetch features. A local <file>.density.bw beside the track file is ' +
144
+ 'attached without the flag; a URL cannot be probed, so a remote sidecar needs ' +
145
+ 'it. The adapters carrying the slot are BamAdapter, CramAdapter, ' +
146
+ 'HtsgetBamAdapter, Gff3TabixAdapter, GtfTabixAdapter, BedTabixAdapter, ' +
147
+ 'BigBedAdapter, VcfTabixAdapter and SplitVcfTabixAdapter.\n\n' +
137
148
  'For pairwise synteny adapters (PAF/Delta/Chain) --assemblyNames is ' +
138
149
  'query,target — the reverse of the minimap2/nucmer input order. For the ' +
139
- 'all-vs-all adapters (AllVsAllPAFAdapter, AllVsAllIndexedPAFAdapter) it is ' +
140
- 'instead the full list of assemblies the file covers, in any order, since ' +
141
- 'one all-vs-all file backs every pair.';
150
+ 'multi-genome PAF adapters (MultiGenomePAFAdapter, MultiGenomeIndexedPAFAdapter) it is ' +
151
+ 'instead the full list of assemblies the file covers, in any order.';
142
152
  const examples = [
143
153
  '# copy /path/to/my.bam and /path/to/my.bam.bai to current directory and adds track to config.json',
144
154
  '$ jbrowse add-track /path/to/my.bam --load copy',
@@ -166,6 +176,9 @@ export async function run(args) {
166
176
  '',
167
177
  '# ...or from a sources.json carrying per-row name/color for each BigWig',
168
178
  '$ jbrowse add-track --multiwig sources.json --name "CATlas ATAC"',
179
+ '',
180
+ '# attach a remote density sidecar (a local genes.gff3.density.bw needs no flag)',
181
+ '$ jbrowse add-track https://mywebsite.com/genes.gff3.gz --density https://mywebsite.com/genes.gff3.density.bw',
169
182
  ];
170
183
  if (flags.help) {
171
184
  printHelp({
@@ -187,7 +200,7 @@ export async function run(args) {
187
200
  const mapLocation = (p) => wrap(mapLocationForFiles(p, load, subDir));
188
201
  // build the adapter (and the set of files to load) up front, so the track-arg
189
202
  // and load validation runs before we touch the config on disk
190
- const { adapter, files, trackType, trackId } = multiwig
203
+ const { adapter: builtAdapter, files, trackType, trackId, } = multiwig
191
204
  ? buildMultiWiggleTrack({
192
205
  sources: multiwig,
193
206
  mapLocation,
@@ -207,6 +220,15 @@ export async function run(args) {
207
220
  trackType: flags.trackType,
208
221
  trackId: flags.trackId,
209
222
  });
223
+ const { adapter, file: densityFile } = withDensityAdapter({
224
+ adapter: builtAdapter,
225
+ location,
226
+ density: flags.density,
227
+ makeLocation: mapLocation,
228
+ });
229
+ if (densityFile !== undefined && !load) {
230
+ throw new Error(`--density names a local file (${densityFile}); pass --load to say how it is placed beside the config`);
231
+ }
210
232
  const baseConfigObj = config ? parseConfigFlag(config) : undefined;
211
233
  const displayDefaults = mergeDisplayDefaults({
212
234
  configObj: baseConfigObj,
@@ -245,7 +267,7 @@ export async function run(args) {
245
267
  });
246
268
  const updatedConfig = { ...configContents, tracks };
247
269
  await loadFiles({
248
- files,
270
+ files: [...files, densityFile],
249
271
  destDir: configDir,
250
272
  mode: load,
251
273
  subDir,