@jbrowse/cli 5.0.0-beta.11 → 5.0.0-beta.14
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 +23 -42
- package/dist/commands/add-track-utils/adapter-utils.js +0 -45
- package/dist/commands/add-track.js +7 -30
- package/dist/commands/make-density/density-generator.js +1 -1
- package/dist/commands/make-density/index.js +14 -26
- package/dist/commands/make-pif/index.js +1 -1
- package/dist/commands/text-index/file-list.js +1 -1
- package/dist/commands/validate/configManifest.generated.js +1242 -326
- package/dist/commands/validate/configSchema.generated.js +11066 -3708
- package/dist/commands/validate/index.js +5 -4
- package/dist/commands/validate/markRules/colorScale.js +157 -28
- package/dist/commands/validate/markRules/markProblems.js +49 -99
- package/dist/commands/validate/markRules/markRuleFacts.js +1 -1
- package/dist/commands/validate/markRules/markSpecs.js +4 -2
- package/dist/commands/validate/markRules/markVocabulary.js +21 -24
- package/dist/commands/validate/markRules/stepChannels.js +13 -47
- package/dist/commands/validate/markRules/stepOutputs.js +202 -0
- package/dist/commands/validate/schemaValidate.js +7 -4
- package/dist/commands/validate/validateConfig.js +54 -33
- package/dist/index.js +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/commands/shared/density.js +0 -12
- package/dist/commands/validate/markRules/aggregateFieldName.js +0 -9
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// Generated by scripts/generateMarkRules.ts from plugins/marks/src/LinearMarkDisplay/markVocabulary.ts: do not edit, `pnpm autogen` rewrites it.
|
|
2
2
|
/**
|
|
3
3
|
* The names a `marks` list is written in and the ones a slot left off reads
|
|
4
|
-
* as, in a module that imports
|
|
4
|
+
* as, in a module that imports only the rule list's seam: the config schema declares its
|
|
5
5
|
* enumerations and defaults from here, the rule list reads the same constants,
|
|
6
|
-
* and `jbrowse validate` carries a copy of both.
|
|
6
|
+
* and `jbrowse validate` carries a copy of both. What a step writes, and
|
|
7
|
+
* the defaults it writes under, are the step table's.
|
|
7
8
|
*/
|
|
9
|
+
export { BIN_OVERLAP_FIELD, DEFAULT_AGGREGATE_OP, DEFAULT_BIN_AS, DEFAULT_COVERAGE_AS, DEFAULT_FORMULA_AS, DEFAULT_PILEUP_AS, } from './markRuleFacts.js';
|
|
8
10
|
export const MARK_TYPES = [
|
|
9
11
|
'bar',
|
|
10
12
|
'point',
|
|
@@ -26,34 +28,29 @@ export const DEFAULT_LINK_SHAPE = 'dome';
|
|
|
26
28
|
export const DEFAULT_LINK_STROKE_PX = 2;
|
|
27
29
|
/** The fraction of its row a span fills where its `rowProportion` is left unwritten. */
|
|
28
30
|
export const DEFAULT_ROW_PROPORTION = 1;
|
|
29
|
-
/** The cuts a threshold
|
|
31
|
+
/** The cuts a threshold color paints, render-core's `MAX_COLOR_CUTS`. */
|
|
30
32
|
export const MAX_THRESHOLD_CUTS = 8;
|
|
31
33
|
export const SIZE_SCALES = ['linear', 'log'];
|
|
32
|
-
export const MARK_SOURCES = ['features', 'density'];
|
|
33
|
-
export const DEFAULT_MARK_SOURCE = 'features';
|
|
34
|
-
export const DEFAULT_FORMULA_AS = 'value';
|
|
35
34
|
export const DEFAULT_BIN_FIELD = 'start';
|
|
36
|
-
export const DEFAULT_BIN_AS = ['start', 'end'];
|
|
37
|
-
/** What a `bin` over `fields` writes on each piece: the bases of its interval inside its bin. */
|
|
38
|
-
export const BIN_OVERLAP_FIELD = 'overlap';
|
|
39
|
-
export const DEFAULT_COVERAGE_AS = 'coverage';
|
|
40
35
|
export const DEFAULT_FLATTEN_FIELD = 'subfeatures';
|
|
41
36
|
export const DEFAULT_CELLS_FIELD = 'seq';
|
|
42
|
-
/**
|
|
43
|
-
export const
|
|
44
|
-
|
|
37
|
+
/** The rows before and after each row a `window` reads: a moving average over 21. */
|
|
38
|
+
export const DEFAULT_WINDOW_FRAME = [-10, 10];
|
|
39
|
+
/**
|
|
40
|
+
* Whether a `window`'s `frame` is what the step reads: two whole row offsets,
|
|
41
|
+
* either `null` for unbounded, the first not past the second.
|
|
42
|
+
*/
|
|
43
|
+
export function isWindowFrame(frame) {
|
|
44
|
+
if (!Array.isArray(frame) || frame.length !== 2) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const [before, after] = frame;
|
|
48
|
+
const end = (v) => v === null || Number.isInteger(v);
|
|
49
|
+
return (end(before) &&
|
|
50
|
+
end(after) &&
|
|
51
|
+
(before === null || after === null || Number(before) <= Number(after)));
|
|
52
|
+
}
|
|
45
53
|
export const DEFAULT_PILEUP_FIELDS = ['start', 'end'];
|
|
46
54
|
export const DEFAULT_TEXT_FIELD = 'name';
|
|
47
55
|
export const DEFAULT_X2 = 'end';
|
|
48
|
-
/** What a `mate` step writes on every feature it answers. */
|
|
49
|
-
export const MATE_FIELDS = [
|
|
50
|
-
'mate.refName',
|
|
51
|
-
'mate.start',
|
|
52
|
-
'mate.end',
|
|
53
|
-
'mate.mateDirection',
|
|
54
|
-
'mateDirection',
|
|
55
|
-
'alt',
|
|
56
|
-
'svType',
|
|
57
|
-
];
|
|
58
56
|
export const AGGREGATE_OPS = ['count', 'sum', 'mean', 'min', 'max'];
|
|
59
|
-
export const DEFAULT_AGGREGATE_OP = 'count';
|
|
@@ -1,55 +1,21 @@
|
|
|
1
1
|
// Generated by scripts/generateMarkRules.ts from plugins/marks/src/LinearMarkDisplay/stepChannels.ts: do not edit, `pnpm autogen` rewrites it.
|
|
2
|
-
import {
|
|
3
|
-
import { DEFAULT_AGGREGATE_OP, DEFAULT_COVERAGE_AS, DEFAULT_PILEUP_AS, } from './markVocabulary.js';
|
|
2
|
+
import { stepsOutput } from './markRuleFacts.js';
|
|
4
3
|
/** The far end a `mate` step writes, as `x2` reads a position on another sequence. */
|
|
5
4
|
export const MATE_X2 = { chrom: 'mate.refName', pos: 'mate.start' };
|
|
6
|
-
function outputOf(step, fallback) {
|
|
7
|
-
return typeof step.as === 'string' && step.as !== '' ? step.as : fallback;
|
|
8
|
-
}
|
|
9
5
|
/**
|
|
10
6
|
* What {@link StepChannels} a mark's steps leave it, read over the display's,
|
|
11
|
-
* the facet's and then the mark's own steps in the order they run.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* summaries names no `y`.
|
|
7
|
+
* the facet's and then the mark's own steps in the order they run. A step
|
|
8
|
+
* that makes its features anew keeps a channel only where it keeps the field
|
|
9
|
+
* that channel read, as an `aggregate` grouped by a pileup's `row` does.
|
|
15
10
|
*/
|
|
16
11
|
export function stepChannels(steps) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
case 'aggregate': {
|
|
28
|
-
const [only, ...rest] = step.ops ?? [];
|
|
29
|
-
channels =
|
|
30
|
-
only && rest.length === 0
|
|
31
|
-
? {
|
|
32
|
-
y: aggregateFieldName({
|
|
33
|
-
op: only.op ?? DEFAULT_AGGREGATE_OP,
|
|
34
|
-
field: only.field,
|
|
35
|
-
as: only.as,
|
|
36
|
-
}),
|
|
37
|
-
}
|
|
38
|
-
: {};
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
case 'pileup': {
|
|
42
|
-
channels = { ...channels, row: outputOf(step, DEFAULT_PILEUP_AS) };
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
case 'mate': {
|
|
46
|
-
channels = { ...channels, x2: MATE_X2 };
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
default: {
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return channels;
|
|
12
|
+
const { channels, surviving } = stepsOutput(steps);
|
|
13
|
+
const y = channels.get('y');
|
|
14
|
+
const row = channels.get('row');
|
|
15
|
+
const x2 = channels.has('x2') && (!surviving || surviving.has(MATE_X2.pos));
|
|
16
|
+
return {
|
|
17
|
+
...(y === undefined ? {} : { y }),
|
|
18
|
+
...(row === undefined ? {} : { row }),
|
|
19
|
+
...(x2 ? { x2: MATE_X2 } : {}),
|
|
20
|
+
};
|
|
55
21
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Generated by scripts/generateMarkRules.ts from packages/core/src/util/stepOutputs.ts: do not edit, `pnpm autogen` rewrites it.
|
|
2
|
+
/**
|
|
3
|
+
* What each transform step does to the rows it reads, stated once for the
|
|
4
|
+
* runtime's facet split, the validator, the field pickers and the channels a
|
|
5
|
+
* mark adopts by default. Imports nothing, so the validator's copy of the mark
|
|
6
|
+
* rule list carries it unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Every step type answers three things:
|
|
9
|
+
* - `rowwise`: it answers each row from that row alone and in order, so a
|
|
10
|
+
* facet split may run ahead of it.
|
|
11
|
+
* - `replacesRows`: it makes its rows anew (an aggregate, a coverage), so a
|
|
12
|
+
* field survives it only where it writes that field or keeps it as a group.
|
|
13
|
+
* - `writes`: the fields it sets, each with the kind of value it holds and the
|
|
14
|
+
* channel a mark leaving that channel unwritten reads it through.
|
|
15
|
+
*/
|
|
16
|
+
/** The field an aggregate op writes: `as` where named, else `count` for a count, else `<op>_<field>`. */
|
|
17
|
+
export function aggregateFieldName({ op, field, as, }) {
|
|
18
|
+
return as || (op === 'count' || !field ? op : `${op}_${field}`);
|
|
19
|
+
}
|
|
20
|
+
export const DEFAULT_FORMULA_AS = 'value';
|
|
21
|
+
export const DEFAULT_COVERAGE_AS = 'coverage';
|
|
22
|
+
export const DEFAULT_PILEUP_AS = 'row';
|
|
23
|
+
export const DEFAULT_AGGREGATE_OP = 'count';
|
|
24
|
+
export const DEFAULT_BIN_AS = ['start', 'end'];
|
|
25
|
+
/** What a `bin` over `fields` writes on each piece: the bases of its interval inside its bin. */
|
|
26
|
+
export const BIN_OVERLAP_FIELD = 'overlap';
|
|
27
|
+
/** What a `cells` step writes: `state` on every cell, `base` on a mismatch or an insertion, `match` off a gap or a reference `N`, `length` on an insertion. */
|
|
28
|
+
export const CELLS_FIELDS = ['state', 'base', 'match', 'length'];
|
|
29
|
+
/** What a `mate` step writes on every feature it answers. */
|
|
30
|
+
export const MATE_FIELDS = [
|
|
31
|
+
'mate.refName',
|
|
32
|
+
'mate.start',
|
|
33
|
+
'mate.end',
|
|
34
|
+
'mate.mateDirection',
|
|
35
|
+
'mateDirection',
|
|
36
|
+
'alt',
|
|
37
|
+
'svType',
|
|
38
|
+
];
|
|
39
|
+
/** Where a row stands, which every row keeps through every step. */
|
|
40
|
+
export const POSITION_FIELDS = ['refName', 'start', 'end'];
|
|
41
|
+
function named(as, fallback) {
|
|
42
|
+
return typeof as === 'string' && as !== '' ? as : fallback;
|
|
43
|
+
}
|
|
44
|
+
function binEdges(step) {
|
|
45
|
+
const { as } = step;
|
|
46
|
+
return Array.isArray(as) && as.length === 2 ? as : DEFAULT_BIN_AS;
|
|
47
|
+
}
|
|
48
|
+
// A single op names the `y` its mark reads; two leave the mark to choose.
|
|
49
|
+
function summaries(step) {
|
|
50
|
+
const ops = step.ops ?? [];
|
|
51
|
+
return ops.map(({ op = DEFAULT_AGGREGATE_OP, field, as }) => ({
|
|
52
|
+
field: aggregateFieldName({ op, field, as }),
|
|
53
|
+
kind: 'numeric',
|
|
54
|
+
...(ops.length === 1 ? { fills: 'y' } : {}),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
const kept = (field) => ({ field, kind: 'unknown' });
|
|
58
|
+
export const STEP_OUTPUTS = {
|
|
59
|
+
filter: { rowwise: true, replacesRows: false, writes: () => [] },
|
|
60
|
+
formula: {
|
|
61
|
+
rowwise: true,
|
|
62
|
+
replacesRows: false,
|
|
63
|
+
writes: step => [
|
|
64
|
+
{ field: named(step.as, DEFAULT_FORMULA_AS), kind: 'numeric' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
bin: {
|
|
68
|
+
rowwise: true,
|
|
69
|
+
replacesRows: false,
|
|
70
|
+
writes: step => [
|
|
71
|
+
...binEdges(step).map(field => ({ field, kind: 'numeric' })),
|
|
72
|
+
...(step.fields?.length === 2
|
|
73
|
+
? [{ field: BIN_OVERLAP_FIELD, kind: 'numeric' }]
|
|
74
|
+
: []),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
cells: {
|
|
78
|
+
rowwise: true,
|
|
79
|
+
replacesRows: false,
|
|
80
|
+
writes: () => [
|
|
81
|
+
{ field: 'start', kind: 'numeric' },
|
|
82
|
+
{ field: 'end', kind: 'numeric' },
|
|
83
|
+
{ field: 'state', kind: 'categorical' },
|
|
84
|
+
{ field: 'base', kind: 'categorical' },
|
|
85
|
+
{ field: 'match', kind: 'numeric' },
|
|
86
|
+
{ field: 'length', kind: 'numeric' },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
aggregate: {
|
|
90
|
+
rowwise: false,
|
|
91
|
+
replacesRows: true,
|
|
92
|
+
writes: (step, edges) => [
|
|
93
|
+
...(step.groupby?.length ? step.groupby : edges).map(kept),
|
|
94
|
+
...summaries(step),
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
window: { rowwise: false, replacesRows: false, writes: summaries },
|
|
98
|
+
coverage: {
|
|
99
|
+
rowwise: false,
|
|
100
|
+
replacesRows: true,
|
|
101
|
+
writes: step => [
|
|
102
|
+
{
|
|
103
|
+
field: named(step.as, DEFAULT_COVERAGE_AS),
|
|
104
|
+
kind: 'numeric',
|
|
105
|
+
fills: 'y',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
pileup: {
|
|
110
|
+
rowwise: false,
|
|
111
|
+
replacesRows: false,
|
|
112
|
+
writes: step => [
|
|
113
|
+
{
|
|
114
|
+
field: named(step.as, DEFAULT_PILEUP_AS),
|
|
115
|
+
kind: 'numeric',
|
|
116
|
+
fills: 'row',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
flatten: {
|
|
121
|
+
rowwise: false,
|
|
122
|
+
replacesRows: false,
|
|
123
|
+
writes: step => [
|
|
124
|
+
...(step.index ? [{ field: step.index, kind: 'numeric' }] : []),
|
|
125
|
+
...(step.key ? [{ field: step.key, kind: 'categorical' }] : []),
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
mate: {
|
|
129
|
+
rowwise: false,
|
|
130
|
+
replacesRows: false,
|
|
131
|
+
writes: () => MATE_FIELDS.map(field => ({
|
|
132
|
+
field,
|
|
133
|
+
kind: field === 'mate.start' || field === 'mate.end'
|
|
134
|
+
? 'numeric'
|
|
135
|
+
: 'categorical',
|
|
136
|
+
...(field === 'mate.refName' ? { fills: 'x2' } : {}),
|
|
137
|
+
})),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
function outputOf(step) {
|
|
141
|
+
return Object.hasOwn(STEP_OUTPUTS, step.type)
|
|
142
|
+
? STEP_OUTPUTS[step.type]
|
|
143
|
+
: undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The fields one step writes. A step this table does not know writes nothing
|
|
147
|
+
* it can name.
|
|
148
|
+
*/
|
|
149
|
+
export function stepWrites(step, edges = DEFAULT_BIN_AS) {
|
|
150
|
+
return outputOf(step)?.writes(step, edges) ?? [];
|
|
151
|
+
}
|
|
152
|
+
/** Whether a step answers each row from that row alone, in order. */
|
|
153
|
+
export function isRowwise(step) {
|
|
154
|
+
return outputOf(step)?.rowwise ?? false;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* What a run of steps leaves: every field written, the last write of each
|
|
158
|
+
* winning, and, where a step made the rows anew, the only fields that survive
|
|
159
|
+
* it (`undefined` while the input's own fields all do). `channels` are what
|
|
160
|
+
* the output fills where a mark leaves them unwritten.
|
|
161
|
+
*/
|
|
162
|
+
export function stepsOutput(steps) {
|
|
163
|
+
let edges = DEFAULT_BIN_AS;
|
|
164
|
+
let surviving;
|
|
165
|
+
const written = new Map();
|
|
166
|
+
let channels = new Map();
|
|
167
|
+
for (const step of steps) {
|
|
168
|
+
const output = step && outputOf(step);
|
|
169
|
+
if (!step || !output) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const writes = output.writes(step, edges);
|
|
173
|
+
if (output.replacesRows) {
|
|
174
|
+
surviving = new Set(POSITION_FIELDS);
|
|
175
|
+
for (const [field] of written) {
|
|
176
|
+
if (!writes.some(w => w.field === field)) {
|
|
177
|
+
written.delete(field);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const w of writes) {
|
|
181
|
+
surviving.add(w.field);
|
|
182
|
+
}
|
|
183
|
+
channels = new Map([...channels].filter(([, f]) => surviving.has(f)));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
for (const w of writes) {
|
|
187
|
+
surviving?.add(w.field);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const w of writes) {
|
|
191
|
+
const held = written.get(w.field);
|
|
192
|
+
written.set(w.field, w.kind === 'unknown' && held ? { ...held, field: w.field } : w);
|
|
193
|
+
if (w.fills) {
|
|
194
|
+
channels.set(w.fills, w.field);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (step.type === 'bin') {
|
|
198
|
+
edges = binEdges(step);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { written: [...written.values()], surviving, channels };
|
|
202
|
+
}
|
|
@@ -68,7 +68,7 @@ function describe(schema) {
|
|
|
68
68
|
if (name === 'JexlString') {
|
|
69
69
|
return 'a "jexl:" expression';
|
|
70
70
|
}
|
|
71
|
-
if (name === 'CssColor') {
|
|
71
|
+
if (name === 'CssColor' || name === 'CssColorEntry') {
|
|
72
72
|
return 'a CSS color (a name like "red", "#rrggbb", "rgb()" or "hsl()")';
|
|
73
73
|
}
|
|
74
74
|
if (name) {
|
|
@@ -175,8 +175,8 @@ function unknownKeyMessage(key, schema, accepted) {
|
|
|
175
175
|
if (title.endsWith('TrackEntry')) {
|
|
176
176
|
return `"${key}" is neither a config slot nor a property of ${title.replace(/TrackEntry$/, '')}${guess} — the entry's keys are folded onto that display, and one it does not declare silently does nothing`;
|
|
177
177
|
}
|
|
178
|
-
if (schema['x-closed']
|
|
179
|
-
return `unknown slot "${key}"${guess} — ${title} takes ${listed(accepted)}
|
|
178
|
+
if (schema['x-closed']) {
|
|
179
|
+
return `unknown slot "${key}"${guess} — ${title} takes ${listed(accepted)}; JBrowse warns on the console and loads without it, so this setting does nothing`;
|
|
180
180
|
}
|
|
181
181
|
return `unknown slot "${key}"${guess} — JBrowse ignores keys it does not declare, so this setting silently does nothing`;
|
|
182
182
|
}
|
|
@@ -378,7 +378,7 @@ function explain(data, errors, problems, depth) {
|
|
|
378
378
|
break;
|
|
379
379
|
}
|
|
380
380
|
case 'pattern':
|
|
381
|
-
emit(where, error.schemaPath
|
|
381
|
+
emit(where, /\/CssColor(?:Entry)?\/pattern$/.test(error.schemaPath)
|
|
382
382
|
? `expected ${describe({ $ref: '#/$defs/CssColor' })}, got ${JSON.stringify(error.data)}`
|
|
383
383
|
: `expected a string matching /${String(parent.pattern)}/, got ${JSON.stringify(error.data)}`);
|
|
384
384
|
break;
|
|
@@ -422,4 +422,7 @@ export function hasDeclaredShape(data, pointer) {
|
|
|
422
422
|
* beside them, the way `LinearGenomeViewPlugin` is core's.
|
|
423
423
|
*/
|
|
424
424
|
export const rootConfigurationSlots = new Set(Object.keys(defs.RootConfiguration.properties));
|
|
425
|
+
export function hasSchemaDefinition(name) {
|
|
426
|
+
return Object.hasOwn(defs, name);
|
|
427
|
+
}
|
|
425
428
|
export { schemaId as configSchemaUrl };
|
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
// cross-references a schema cannot express and the warnings for stale
|
|
4
4
|
// spellings a migration still rewrites.
|
|
5
5
|
//
|
|
6
|
-
// Why a validator at all, given MST type-checks a config on load:
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Why a validator at all, given MST type-checks a config on load: a key no
|
|
7
|
+
// schema declares does nothing, named on the console by a display and dropped
|
|
8
|
+
// in silence by a track, an adapter or a session snapshot, so a misspelt slot
|
|
9
|
+
// leaves the track loading normally.
|
|
9
10
|
//
|
|
10
11
|
// Pure: no filesystem, no process exit. The command wrapper owns both.
|
|
11
|
-
import { fileNameOf, isLooseTrackConfig, matchFormat, } from '@jbrowse/add-track-core';
|
|
12
|
+
import { fileNameOf, isLooseTrackConfig, matchFormat, trackTypeForAdapter, } from '@jbrowse/add-track-core';
|
|
12
13
|
import { configManifest } from './configManifest.generated.js';
|
|
13
14
|
import { displayDefaultsForTrackType } from './displayDefaultKeys.js';
|
|
14
15
|
import { isRecord, liftToSnapshot } from './liftConfig.js';
|
|
15
|
-
import { colorProblems, scaleEndProblems } from './markRules/colorScale.js';
|
|
16
|
+
import { colorProblems, colorSlotsOf, scaleEndProblems, scaleEndsOf, withImpliedField, } from './markRules/colorScale.js';
|
|
16
17
|
import { markProblems } from './markRules/markProblems.js';
|
|
17
18
|
import { hasDeclaredShape, rootConfigurationSlots, schemaProblems, } from './schemaValidate.js';
|
|
18
19
|
import { didYouMean } from './suggest.js';
|
|
@@ -44,6 +45,12 @@ class Report {
|
|
|
44
45
|
const SELF_SUPPLIED = {
|
|
45
46
|
sequenceAdapter: '`sequenceAdapter` is set by hand. JBrowse takes the sequence from the assembly the track is displayed against, so this is only needed to read some OTHER sequence — and it pins the track to that source even when the assembly changes. Delete it unless that is what you meant',
|
|
46
47
|
};
|
|
48
|
+
// What replaced a dropped legacy key, where there is something to say: a
|
|
49
|
+
// config written before v4 carries a `sequenceAdapter` on its BAM and CRAM
|
|
50
|
+
// adapters.
|
|
51
|
+
const DROPPED = {
|
|
52
|
+
sequenceAdapter: 'JBrowse takes the sequence from the assembly the track is displayed against. ',
|
|
53
|
+
};
|
|
47
54
|
// The warnings a slot table carries: a legacy key or value a migration
|
|
48
55
|
// rewrites, and a declared key JBrowse fills in for itself.
|
|
49
56
|
function warnSlots(obj, entry, where, report) {
|
|
@@ -56,7 +63,9 @@ function warnSlots(obj, entry, where, report) {
|
|
|
56
63
|
report.warn(`${where}.${key}`, `${JSON.stringify(value)} is a legacy value of "${key}" that a migration rewrites — the config loads, but the current spelling is one of the enum's members`);
|
|
57
64
|
}
|
|
58
65
|
else if (!declared.has(key) && entry.legacyKeys?.includes(key)) {
|
|
59
|
-
report.warn(`${where}.${key}`,
|
|
66
|
+
report.warn(`${where}.${key}`, entry.droppedKeys?.includes(key)
|
|
67
|
+
? `"${key}" is a legacy key that is never read. ${DROPPED[key] ?? ''}The config loads, and the key can be deleted`
|
|
68
|
+
: `"${key}" is a legacy key that a migration rewrites into current slots — the config loads, but check the settings inside it landed`);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
for (const slot of entry.slots) {
|
|
@@ -290,7 +299,7 @@ function declaredEntries(lifted, display, slot, slots) {
|
|
|
290
299
|
return (slots.find(s => s.name === slot)?.defaultEntries ?? []);
|
|
291
300
|
}
|
|
292
301
|
return Array.isArray(list)
|
|
293
|
-
? list.map((entry, i) => hasDeclaredShape(written[i], `${SLOTS_POINTER}/${slot}
|
|
302
|
+
? list.map((entry, i) => hasDeclaredShape([written[i]], `${SLOTS_POINTER}/${slot}`)
|
|
294
303
|
? entry
|
|
295
304
|
: undefined)
|
|
296
305
|
: [];
|
|
@@ -337,31 +346,22 @@ function checkMarkDisplay(display, manifest, where, report) {
|
|
|
337
346
|
});
|
|
338
347
|
}
|
|
339
348
|
}
|
|
340
|
-
// A problem the
|
|
349
|
+
// A problem the color and scale rules and the mark rule list both reach, such
|
|
341
350
|
// as scales.y's ends on a mark display, is reported once.
|
|
342
351
|
function pushOnce(report, problem) {
|
|
343
352
|
if (!report.problems.some(p => p.where === problem.where && p.rule === problem.rule)) {
|
|
344
353
|
report.problems.push(problem);
|
|
345
354
|
}
|
|
346
355
|
}
|
|
347
|
-
function numberOf(value) {
|
|
348
|
-
return typeof value === 'number' ? value : undefined;
|
|
349
|
-
}
|
|
350
|
-
function scaleEndsOf(scale) {
|
|
351
|
-
return {
|
|
352
|
-
domainMin: numberOf(scale.domainMin),
|
|
353
|
-
domainMax: numberOf(scale.domainMax),
|
|
354
|
-
domainQuantile: numberOf(scale.domainQuantile),
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
356
|
function scalesOf(scales) {
|
|
358
|
-
|
|
357
|
+
const { y } = scales;
|
|
358
|
+
return isRecord(y) ? { y: scaleEndsOf(name => y[name]) } : {};
|
|
359
359
|
}
|
|
360
360
|
function declaredMembers(written, slot) {
|
|
361
361
|
const declared = new Set((slot.subSlots ?? []).map(s => s.name));
|
|
362
362
|
return Object.fromEntries(Object.entries(written).filter(([key]) => declared.has(key)));
|
|
363
363
|
}
|
|
364
|
-
// What the display's
|
|
364
|
+
// What the display's color objects and its value scale say together that it
|
|
365
365
|
// cannot draw as written: its schema's fieldPresets stand in for the plugin
|
|
366
366
|
// code the CLI does not run, and every quantitative scale's ends answer to
|
|
367
367
|
// one rule.
|
|
@@ -385,19 +385,15 @@ function checkScaleSlots(display, slots, where, report) {
|
|
|
385
385
|
}
|
|
386
386
|
const members = declaredMembers(written, slot);
|
|
387
387
|
if (slot.fieldPresets) {
|
|
388
|
-
for (const problem of colorProblems(
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
scale: typeof members.scale === 'string' ? members.scale : undefined,
|
|
392
|
-
domain: Array.isArray(members.domain) ? members.domain : undefined,
|
|
393
|
-
range: Array.isArray(members.range) ? members.range : undefined,
|
|
394
|
-
labels: Array.isArray(members.labels) ? members.labels : undefined,
|
|
395
|
-
}, slot.fieldPresets)) {
|
|
388
|
+
for (const problem of colorProblems(withImpliedField(colorSlotsOf(name => name === 'field'
|
|
389
|
+
? (members.field ?? slot.fieldDefault)
|
|
390
|
+
: members[name]), slot.impliedField), slot.fieldPresets)) {
|
|
396
391
|
warn(`${where}.${slot.name}`, problem);
|
|
397
392
|
}
|
|
398
393
|
}
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
const { y } = members;
|
|
395
|
+
if (slot.name === 'scales' && isRecord(y)) {
|
|
396
|
+
for (const problem of scaleEndProblems(scaleEndsOf(name => y[name]))) {
|
|
401
397
|
warn(`${where}.scales.y`, problem);
|
|
402
398
|
}
|
|
403
399
|
}
|
|
@@ -408,6 +404,30 @@ function checkScaleSlots(display, slots, where, report) {
|
|
|
408
404
|
// entry. Only a mark display declares `marks`, so the walk is over the whole
|
|
409
405
|
// file, past a place where the schema refused the key: a session display node
|
|
410
406
|
// drops it.
|
|
407
|
+
// a track's type as written, or as a loose `{ trackId, uri }` entry's file
|
|
408
|
+
// name decides it, the way add-track does
|
|
409
|
+
function trackTypeOf(node) {
|
|
410
|
+
if (typeof node.type === 'string') {
|
|
411
|
+
return node.type;
|
|
412
|
+
}
|
|
413
|
+
if (typeof node.uri !== 'string') {
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
const fileName = fileNameOf(node.uri);
|
|
417
|
+
const format = matchFormat(fileName, typeof node.adapterType === 'string' ? node.adapterType : undefined);
|
|
418
|
+
return (format?.trackType ??
|
|
419
|
+
(format && 'adapterType' in format.spec
|
|
420
|
+
? trackTypeForAdapter(format.spec.adapterType, fileName)
|
|
421
|
+
: undefined));
|
|
422
|
+
}
|
|
423
|
+
// The shorthand router hands a `displayDefaults` key only to the displays
|
|
424
|
+
// whose slot takes its value (`collectDisplayOverrides`), so a categorical
|
|
425
|
+
// color on a FeatureTrack never reaches its quantitative display's `score`
|
|
426
|
+
// presets. The same cut here, through each display's own schema branch.
|
|
427
|
+
function displayDefaultsReaching(display, defaults) {
|
|
428
|
+
return Object.fromEntries(Object.entries(defaults).filter(([key, value]) => schemaProblems({ type: display, [key]: value }, `/$defs/${display}`)
|
|
429
|
+
.length === 0));
|
|
430
|
+
}
|
|
411
431
|
function checkMarkDisplays(node, manifest, where, report) {
|
|
412
432
|
if (Array.isArray(node)) {
|
|
413
433
|
for (const [i, item] of node.entries()) {
|
|
@@ -421,10 +441,11 @@ function checkMarkDisplays(node, manifest, where, report) {
|
|
|
421
441
|
if (slots) {
|
|
422
442
|
checkScaleSlots(node, slots, where, report);
|
|
423
443
|
}
|
|
424
|
-
|
|
425
|
-
|
|
444
|
+
const trackType = trackTypeOf(node);
|
|
445
|
+
if (trackType && isRecord(node.displayDefaults)) {
|
|
446
|
+
for (const display of displayDefaultsForTrackType(trackType, manifest)
|
|
426
447
|
.displayTypes) {
|
|
427
|
-
checkScaleSlots(node.displayDefaults, manifest.displays[display]?.slots ?? [], `${where}.displayDefaults`, report);
|
|
448
|
+
checkScaleSlots(displayDefaultsReaching(display, node.displayDefaults), manifest.displays[display]?.slots ?? [], `${where}.displayDefaults`, report);
|
|
428
449
|
}
|
|
429
450
|
}
|
|
430
451
|
if (holdsMarkRules(node, manifest) &&
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ const registry = [
|
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
name: 'make-density',
|
|
54
|
-
summary: 'Counts feature starts per bin into a bigWig
|
|
54
|
+
summary: 'Counts feature starts per bin into a bigWig, with bedGraphToBigWig',
|
|
55
55
|
load: () => import('./commands/make-density/index.js').then(m => m.run),
|
|
56
56
|
},
|
|
57
57
|
{
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '5.0.0-beta.
|
|
1
|
+
export const version = '5.0.0-beta.14';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/cli",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A command line tool for working with JBrowse 2",
|
|
6
6
|
"keywords": [
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"cli-progress": "^3.12.0",
|
|
32
32
|
"serve-handler": "^6.1.7",
|
|
33
33
|
"tmp": "^0.2.7",
|
|
34
|
-
"@jbrowse/add-track-core": "^5.0.0-beta.
|
|
35
|
-
"@jbrowse/
|
|
36
|
-
"@jbrowse/
|
|
34
|
+
"@jbrowse/add-track-core": "^5.0.0-beta.14",
|
|
35
|
+
"@jbrowse/cigar-utils": "^5.0.0-beta.14",
|
|
36
|
+
"@jbrowse/text-indexing-core": "^5.0.0-beta.14"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Where the density sidecar for a data file lives: `<file>.density.bw`, with a
|
|
3
|
-
* `.gz`/`.bgz` stripped first so `genes.gff3.gz` yields `genes.gff3.density.bw`
|
|
4
|
-
* rather than a name carrying the compression of a file it is not compressed
|
|
5
|
-
* like.
|
|
6
|
-
*
|
|
7
|
-
* One rule, two commands: `make-density` writes here by default and `add-track`
|
|
8
|
-
* probes here, so a sidecar built with no `--out` is the one the track picks up.
|
|
9
|
-
*/
|
|
10
|
-
export function densitySidecarPath(file) {
|
|
11
|
-
return `${file.replace(/\.b?gz$/i, '')}.density.bw`;
|
|
12
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Generated by scripts/generateMarkRules.ts from packages/core/src/util/aggregateFieldName.ts: do not edit, `pnpm autogen` rewrites it.
|
|
2
|
-
/**
|
|
3
|
-
* The field an aggregate op writes: `as` where named, else `count` for a
|
|
4
|
-
* count, else `<op>_<field>`. Imports nothing, so the validator's copy of the
|
|
5
|
-
* mark rule list carries it unchanged.
|
|
6
|
-
*/
|
|
7
|
-
export function aggregateFieldName({ op, field, as, }) {
|
|
8
|
-
return as || (op === 'count' || !field ? op : `${op}_${field}`);
|
|
9
|
-
}
|