@jbrowse/cli 5.0.0-beta.4 → 5.0.0-beta.7
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 +7 -0
- package/dist/commands/add-track-utils/track-config.js +3 -3
- package/dist/commands/make-pif/index.js +6 -1
- package/dist/commands/make-pif/pif-generator.js +34 -40
- package/dist/commands/validate/configManifest.generated.js +685 -75
- package/dist/commands/validate/configSchema.generated.js +17706 -0
- package/dist/commands/validate/index.js +8 -1
- package/dist/commands/validate/schemaValidate.js +300 -0
- package/dist/commands/validate/validateConfig.js +76 -278
- package/dist/version.js +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -427,6 +427,13 @@ naming a trackId that does not exist, a duplicate trackId. Exits 1.
|
|
|
427
427
|
Types registered by plugins are not known to this command, so they come through
|
|
428
428
|
as warnings rather than errors.
|
|
429
429
|
|
|
430
|
+
The same checks are published as a JSON Schema at
|
|
431
|
+
https://jbrowse.org/jb2/schema/v5/config.json: a "$schema" line naming it at the
|
|
432
|
+
top of a config.json gives an editor completion and validation for every slot.
|
|
433
|
+
This command runs that schema first, then the cross-references a schema cannot
|
|
434
|
+
express — a trackId a session names that no track defines, an assembly a track
|
|
435
|
+
names that none defines.
|
|
436
|
+
|
|
430
437
|
Examples:
|
|
431
438
|
|
|
432
439
|
# check the config.json in the current directory
|
|
@@ -56,9 +56,9 @@ export function mergeDisplayDefaults({ configObj, color, height, displayDefaults
|
|
|
56
56
|
// An unknown track type accepts everything: it is a plugin's, and this manifest
|
|
57
57
|
// is only the core plugins'.
|
|
58
58
|
//
|
|
59
|
-
// The message names the display
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// The message names the display where the track offers exactly one, and the
|
|
60
|
+
// track otherwise: the keys checked are the union over its displays, so
|
|
61
|
+
// naming one of several would send the reader to look in the wrong place.
|
|
62
62
|
function checkDisplayDefaultKeys(merged, trackType) {
|
|
63
63
|
const { displayTypes, keys } = displayDefaultsForTrackType(trackType);
|
|
64
64
|
if (keys.length === 0) {
|
|
@@ -125,7 +125,7 @@ export async function run(args) {
|
|
|
125
125
|
throw new Error(`PIF sort/index pipeline exited with ${describeExit(exit)}`);
|
|
126
126
|
}
|
|
127
127
|
const { stats } = pass;
|
|
128
|
-
const { samples, rows, skipped } = stats;
|
|
128
|
+
const { samples, rows, cigarRows, skipped } = stats;
|
|
129
129
|
if (rows === 0) {
|
|
130
130
|
discardPart();
|
|
131
131
|
throw new Error(`No valid PAF rows found in ${file ?? 'stdin'} (${skipped} line(s) had fewer than the 12 mandatory PAF columns). Is this a PAF file?`);
|
|
@@ -135,6 +135,11 @@ export async function run(args) {
|
|
|
135
135
|
if (skipped > 0) {
|
|
136
136
|
console.warn(`Warning: skipped ${skipped} of ${rows + skipped} line(s) with fewer than the 12 mandatory PAF columns`);
|
|
137
137
|
}
|
|
138
|
+
if (coarseGap !== undefined && cigarRows === 0) {
|
|
139
|
+
console.warn('Warning: no row carried a CIGAR (cg:Z: or cs:Z:), so the coarse tier repeats ' +
|
|
140
|
+
'the fine tier and doubles the file for nothing. Rebuild with --no-coarse, ' +
|
|
141
|
+
'or align with a CIGAR (minimap2 -c) so low zooms have something cheaper to read.');
|
|
142
|
+
}
|
|
138
143
|
// An all-vs-all PAF that does not state every pair indexes perfectly well and
|
|
139
144
|
// then draws an empty synteny band for each pair the aligner never emitted,
|
|
140
145
|
// which reads as "no homology here" rather than as "never computed". This is
|
|
@@ -5,27 +5,31 @@ import { Transform } from 'node:stream';
|
|
|
5
5
|
import { pipeline } from 'node:stream/promises';
|
|
6
6
|
import { createGunzip } from 'node:zlib';
|
|
7
7
|
import { coarsenCigar, csToCigar, flipCigar, flipCoarseCigar, swapCoarseCigar, swapIndelCigar, } from '@jbrowse/cigar-utils';
|
|
8
|
+
import { version } from '../../version.js';
|
|
8
9
|
// Default gap (bp) of the coarse tier's coarse CIGAR: how far a straight line
|
|
9
10
|
// across one of its runs may be from the alignment's real path. Indels longer
|
|
10
11
|
// than half of it keep their letter; shorter ones fold into the runs. 10kb
|
|
11
12
|
// matches the adapter's default coarseBpPerPxThreshold, so at the zoom where the
|
|
12
13
|
// tier is served the bound is ~1px and a folded indel was sub-pixel anyway.
|
|
13
14
|
export const DEFAULT_COARSE_GAP = 10_000;
|
|
15
|
+
// Generation 2 (2026-09): every row carries `pi:i:`, the input row's index,
|
|
16
|
+
// and the header names the writer.
|
|
17
|
+
export const PIF_FORMAT_VERSION = 2;
|
|
14
18
|
/**
|
|
15
19
|
* The one meta line a PIF carries, sorted first by the C-locale sort and kept
|
|
16
|
-
* by tabix as a header. It states the format generation, the
|
|
17
|
-
* the coarse tier's accuracy bound (`--coarse`), and whether
|
|
18
|
-
* had a CIGAR — the facts a reader cannot recover from the rows.
|
|
20
|
+
* by tabix as a header. It states the format generation, the writer, the
|
|
21
|
+
* tiers written, the coarse tier's accuracy bound (`--coarse`), and whether
|
|
22
|
+
* every input row had a CIGAR — the facts a reader cannot recover from the rows.
|
|
19
23
|
*/
|
|
20
24
|
export function pifHeader(coarseGap, stats) {
|
|
21
25
|
const cigars = stats.cigarRows === 0
|
|
22
26
|
? 'none'
|
|
23
|
-
: stats.cigarRows === stats.rows
|
|
27
|
+
: stats.cigarRows === stats.rows
|
|
24
28
|
? 'all'
|
|
25
29
|
: 'some';
|
|
26
30
|
const tiers = coarseGap === undefined ? 'fine' : 'fine,coarse';
|
|
27
31
|
const bound = coarseGap === undefined ? '' : `\tcoarse:i:${coarseGap}`;
|
|
28
|
-
return `#pif\tversion:i:
|
|
32
|
+
return `#pif\tversion:i:${PIF_FORMAT_VERSION}\twriter:Z:jbrowse-cli/${version}\ttiers:Z:${tiers}${bound}\tcigars:Z:${cigars}\n`;
|
|
29
33
|
}
|
|
30
34
|
function panSNSample(refName) {
|
|
31
35
|
const i = refName.indexOf('#');
|
|
@@ -62,33 +66,23 @@ export function missingPairs({ samples, pairs }) {
|
|
|
62
66
|
}
|
|
63
67
|
return out;
|
|
64
68
|
}
|
|
65
|
-
// The coarse row's `cr:Z:` value, or nothing
|
|
66
|
-
// the
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// still written: the runs are where a lopsided cluster of small indels bends
|
|
74
|
-
// the path, which a straight ribbon across the row would miss by up to the
|
|
75
|
-
// whole cluster.
|
|
76
|
-
//
|
|
77
|
-
// `unbounded` separates the last of those from the rest. The first two leave a
|
|
78
|
-
// tagless row a reader may take as one run within `--coarse`, and it is one; a
|
|
79
|
-
// walk that misses its own corner leaves a row that reads the same way and is
|
|
80
|
-
// not, so the header has to stop saying `cigars:Z:all` on its account.
|
|
69
|
+
// The coarse row's `cr:Z:` value, or nothing when the row has no CIGAR or the
|
|
70
|
+
// fold is a single run that closes on the coordinate columns, which then say it
|
|
71
|
+
// all. A fold of several runs with no kept indel is still written: a lopsided
|
|
72
|
+
// cluster of small indels bends the path by their sum. So is a fold whose walk
|
|
73
|
+
// misses the row's own far corner (clipping ops, a hand-made cg, a cs whose
|
|
74
|
+
// spans don't add up): the fine tier walks that CIGAR from the start corner
|
|
75
|
+
// without checking, and the coarse tier draws the same path only if it walks
|
|
76
|
+
// the same fold.
|
|
81
77
|
function coarseFold({ cigar, coarseGap, ownLen, mateLen, }) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
unbounded: cigar !== undefined && !closed,
|
|
91
|
-
};
|
|
78
|
+
if (cigar === undefined) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
const coarse = coarsenCigar(cigar, coarseGap);
|
|
82
|
+
const closed = coarse.ownLen === ownLen && coarse.mateLen === mateLen;
|
|
83
|
+
return !closed || coarse.gapCount > 0 || coarse.opCount > 1
|
|
84
|
+
? coarse.ops
|
|
85
|
+
: undefined;
|
|
92
86
|
}
|
|
93
87
|
// A PIF row carries exactly ONE alignment string — `cg:Z:`, in the orientation
|
|
94
88
|
// of the perspective it is indexed under. A minimap2 `cs:Z:` is folded into that
|
|
@@ -135,10 +129,14 @@ function processLine(rawLine, coarseGap, stats) {
|
|
|
135
129
|
// rest[0]=num_matches, rest[1]=block_len, rest[2]=mapq, rest[3+]=optional tags
|
|
136
130
|
const [c1, l1, s1, e1, strand, c2, l2, s2, e2, ...rest] = parts;
|
|
137
131
|
addPanSNPair(stats, c1, c2);
|
|
138
|
-
// an incoming cr:Z: (a PIF turned back into PAF, or a tool that
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
132
|
+
// an incoming cr:Z: or pi:i: (a PIF turned back into PAF, or a tool that
|
|
133
|
+
// adopted the tags) is dropped: the coarse tier writes its own fold below,
|
|
134
|
+
// and every row of this alignment gets this pass's row index as `pi`, the
|
|
135
|
+
// one id its four rows share across perspectives and tiers
|
|
136
|
+
const { tags, cigarIdx } = foldCsIntoCg([
|
|
137
|
+
...rest.filter(f => !f.startsWith('cr:') && !f.startsWith('pi:')),
|
|
138
|
+
`pi:i:${stats.rows - 1}`,
|
|
139
|
+
]);
|
|
142
140
|
const cigar = cigarIdx === -1 ? undefined : tags[cigarIdx].slice(5);
|
|
143
141
|
if (cigar !== undefined) {
|
|
144
142
|
stats.cigarRows++;
|
|
@@ -161,15 +159,12 @@ function processLine(rawLine, coarseGap, stats) {
|
|
|
161
159
|
const coarse = cigarIdx === -1 ? tags : tags.filter((_, i) => i !== cigarIdx);
|
|
162
160
|
// the T row's own axis is the target, as the PAF CIGAR is written; the Q row
|
|
163
161
|
// re-orients it for the query, the way the fine tier's cg is
|
|
164
|
-
const
|
|
162
|
+
const cr = coarseFold({
|
|
165
163
|
cigar,
|
|
166
164
|
coarseGap,
|
|
167
165
|
ownLen: +e2 - +s2,
|
|
168
166
|
mateLen: +e1 - +s1,
|
|
169
167
|
});
|
|
170
|
-
if (unbounded) {
|
|
171
|
-
stats.unboundedRows++;
|
|
172
|
-
}
|
|
173
168
|
const tCr = cr === undefined ? [] : [`cr:Z:${cr}`];
|
|
174
169
|
const qCr = cr === undefined
|
|
175
170
|
? []
|
|
@@ -215,7 +210,6 @@ export async function createPIF(filename, stream, coarseGap) {
|
|
|
215
210
|
pairs: new Set(),
|
|
216
211
|
rows: 0,
|
|
217
212
|
cigarRows: 0,
|
|
218
|
-
unboundedRows: 0,
|
|
219
213
|
skipped: 0,
|
|
220
214
|
};
|
|
221
215
|
const transform = makePifTransform(coarseGap, stats);
|