@jbrowse/cli 4.3.0 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +451 -72
- package/dist/base.js +0 -3
- package/dist/bin.js +2 -1
- package/dist/cliFetch.js +3 -0
- package/dist/commands/add-assembly/index.js +46 -19
- package/dist/commands/add-assembly/utils.js +204 -290
- package/dist/commands/add-connection.js +73 -79
- package/dist/commands/add-track-json.js +24 -12
- package/dist/commands/add-track-utils/adapter-utils.js +189 -288
- package/dist/commands/add-track-utils/file-operations.js +27 -11
- package/dist/commands/add-track-utils/multiwig.js +47 -0
- package/dist/commands/add-track-utils/track-config.js +66 -25
- package/dist/commands/add-track-utils/validators.js +36 -20
- package/dist/commands/add-track.js +200 -62
- package/dist/commands/admin-server/index.js +39 -50
- package/dist/commands/admin-server/utils.js +238 -126
- package/dist/commands/create.js +18 -34
- package/dist/commands/make-density/density-generator.js +180 -0
- package/dist/commands/make-density/index.js +139 -0
- package/dist/commands/make-pif/index.js +133 -11
- package/dist/commands/make-pif/pif-generator.js +243 -43
- package/dist/commands/process-utils.js +13 -2
- package/dist/commands/remove-track.js +20 -13
- package/dist/commands/set-default-session.js +35 -31
- package/dist/commands/shared/config-operations.js +3 -10
- package/dist/commands/shared/density.js +12 -0
- package/dist/commands/shared/sort-utils.js +67 -10
- package/dist/commands/shared/validators.js +38 -3
- package/dist/commands/sort-bed.js +2 -33
- package/dist/commands/sort-gff.js +2 -33
- package/dist/commands/text-index/adapter-utils.js +19 -26
- package/dist/commands/text-index/aggregate.js +40 -34
- package/dist/commands/text-index/command.js +37 -7
- package/dist/commands/text-index/config-utils.js +62 -85
- package/dist/commands/text-index/file-list.js +32 -18
- package/dist/commands/text-index/index.js +4 -4
- package/dist/commands/text-index/indexing-utils.js +44 -96
- package/dist/commands/text-index/per-track.js +44 -31
- package/dist/commands/text-index/validators.js +1 -1
- package/dist/commands/upgrade.js +20 -32
- package/dist/commands/validate/configManifest.generated.js +7613 -0
- package/dist/commands/validate/configSchema.generated.js +20400 -0
- package/dist/commands/validate/displayDefaultKeys.js +28 -0
- package/dist/commands/validate/index.js +113 -0
- package/dist/commands/validate/liftConfig.js +34 -0
- package/dist/commands/validate/markRules/aggregateFieldName.js +9 -0
- package/dist/commands/validate/markRules/autoBin.js +27 -0
- package/dist/commands/validate/markRules/colorScale.js +244 -0
- package/dist/commands/validate/markRules/cssColorsLevel4.js +162 -0
- package/dist/commands/validate/markRules/jexlStrings.js +23 -0
- package/dist/commands/validate/markRules/markProblems.js +532 -0
- package/dist/commands/validate/markRules/markRuleFacts.js +6 -0
- package/dist/commands/validate/markRules/markSpecs.js +96 -0
- package/dist/commands/validate/markRules/markVocabulary.js +57 -0
- package/dist/commands/validate/markRules/shapeNameList.js +7 -0
- package/dist/commands/validate/markRules/stepChannels.js +55 -0
- package/dist/commands/validate/schemaValidate.js +425 -0
- package/dist/commands/validate/suggest.js +35 -0
- package/dist/commands/validate/types.js +6 -0
- package/dist/commands/validate/validateConfig.js +502 -0
- package/dist/index.js +104 -50
- package/dist/types/common.js +3 -27
- package/dist/utils.js +309 -128
- package/dist/version.js +1 -1
- package/package.json +8 -14
- package/bin/run +0 -4
- package/bundle/index.js +0 -3686
- package/dist/commands/make-pif/cigar-utils.js +0 -24
- package/dist/commands/make-pif/file-utils.js +0 -30
- package/dist/commands/track-utils.js +0 -74
- package/dist/util.js +0 -35
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { isURL } from
|
|
4
|
-
import { debug, readInlineOrFileJson, readJsonFile,
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isURL } from '../../types/common.js';
|
|
4
|
+
import { STDIN_ARG, debug, ignoreNotFound, isRecord, parseCommaSeparatedString, readInlineOrFileJson, readJsonFile, } from '../../utils.js';
|
|
5
|
+
import { mapLocationForFiles } from '../add-track-utils/track-config.js';
|
|
6
|
+
import { parseConfigFlag, validateLoadAndLocation, } from '../add-track-utils/validators.js';
|
|
7
|
+
import { findAndUpdateOrAdd } from '../shared/config-operations.js';
|
|
8
|
+
const sequenceTypes = new Set([
|
|
9
|
+
'indexedFasta',
|
|
10
|
+
'bgzipFasta',
|
|
11
|
+
'twoBit',
|
|
12
|
+
'chromSizes',
|
|
13
|
+
'custom',
|
|
14
|
+
]);
|
|
15
|
+
function isSequenceType(t) {
|
|
16
|
+
return sequenceTypes.has(t);
|
|
17
|
+
}
|
|
18
|
+
// parseArgs does not enforce the `choices` declared on --type, so an
|
|
19
|
+
// unrecognized value would otherwise be silently dropped and the type guessed
|
|
20
|
+
// from the file extension, masking a user typo. Returning the narrowed value
|
|
21
|
+
// (rather than validating and leaving the caller to re-test the same string)
|
|
22
|
+
// keeps the check and the narrowing in one place.
|
|
23
|
+
export function parseSequenceType(type) {
|
|
24
|
+
if (type === undefined) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
if (!isSequenceType(type)) {
|
|
28
|
+
throw new Error(`--type must be one of: ${[...sequenceTypes].join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
return type;
|
|
31
|
+
}
|
|
32
|
+
function isValidJSON(string) {
|
|
8
33
|
try {
|
|
9
34
|
JSON.parse(string);
|
|
10
35
|
return true;
|
|
@@ -13,299 +38,197 @@ export function isValidJSON(string) {
|
|
|
13
38
|
return false;
|
|
14
39
|
}
|
|
15
40
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
// recognized FASTA extensions, shared by sequence-type guessing and basename
|
|
42
|
+
// stripping so the list lives in one place
|
|
43
|
+
const fastaExts = 'fa|fna|fasta|mfa';
|
|
44
|
+
const fastaRegex = new RegExp(`\\.(${fastaExts})$`, 'i');
|
|
45
|
+
const bgzipFastaRegex = new RegExp(`\\.(${fastaExts})\\.gz$`, 'i');
|
|
46
|
+
const fastaExtRegex = new RegExp(`\\.(${fastaExts})(\\.gz)?$`, 'i');
|
|
47
|
+
function basenameWithoutFastaExt(p) {
|
|
48
|
+
return path.basename(p).replace(fastaExtRegex, '');
|
|
49
|
+
}
|
|
50
|
+
// extension → sequence type, tried in order. The plain-fasta pattern is anchored
|
|
51
|
+
// at the end so it cannot claim a .fa.gz out from under the bgzip one.
|
|
52
|
+
const seqTypeByExtension = [
|
|
53
|
+
[fastaRegex, 'indexedFasta'],
|
|
54
|
+
[bgzipFastaRegex, 'bgzipFasta'],
|
|
55
|
+
[/\.2bit$/i, 'twoBit'],
|
|
56
|
+
[/\.chrom\.sizes$/i, 'chromSizes'],
|
|
57
|
+
[/\.json$/i, 'custom'],
|
|
58
|
+
];
|
|
59
|
+
function guessSequenceType(sequence) {
|
|
60
|
+
const byExtension = seqTypeByExtension.find(([regex]) => regex.test(sequence))?.[1];
|
|
61
|
+
if (byExtension) {
|
|
62
|
+
return byExtension;
|
|
35
63
|
}
|
|
64
|
+
// a custom adapter can also be given as inline JSON, which has no extension
|
|
36
65
|
if (isValidJSON(sequence)) {
|
|
37
66
|
return 'custom';
|
|
38
67
|
}
|
|
39
68
|
throw new Error('Could not determine sequence type automatically, add --type to specify it');
|
|
40
69
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
const seqSpecs = {
|
|
71
|
+
indexedFasta: {
|
|
72
|
+
adapterType: 'IndexedFastaAdapter',
|
|
73
|
+
locField: 'fastaLocation',
|
|
74
|
+
sidecars: [{ field: 'faiLocation', suffix: '.fai' }],
|
|
75
|
+
defaultName: basenameWithoutFastaExt,
|
|
76
|
+
},
|
|
77
|
+
bgzipFasta: {
|
|
78
|
+
adapterType: 'BgzipFastaAdapter',
|
|
79
|
+
locField: 'fastaLocation',
|
|
80
|
+
sidecars: [
|
|
81
|
+
{ field: 'faiLocation', suffix: '.fai' },
|
|
82
|
+
{ field: 'gziLocation', suffix: '.gzi' },
|
|
83
|
+
],
|
|
84
|
+
defaultName: basenameWithoutFastaExt,
|
|
85
|
+
},
|
|
86
|
+
twoBit: {
|
|
87
|
+
adapterType: 'TwoBitAdapter',
|
|
88
|
+
locField: 'twoBitLocation',
|
|
89
|
+
sidecars: [],
|
|
90
|
+
defaultName: p => path.basename(p, '.2bit'),
|
|
91
|
+
},
|
|
92
|
+
chromSizes: {
|
|
93
|
+
adapterType: 'ChromSizesAdapter',
|
|
94
|
+
locField: 'chromSizesLocation',
|
|
95
|
+
sidecars: [],
|
|
96
|
+
defaultName: p => path.basename(p, '.chrom.sizes'),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
async function customSequenceAdapter(argsSequence, name) {
|
|
100
|
+
const adapter = await readInlineOrFileJson(argsSequence);
|
|
101
|
+
debug(`Custom adapter: ${JSON.stringify(adapter)}`);
|
|
102
|
+
if (!name) {
|
|
103
|
+
// neither inline JSON nor stdin carries a filename to name the assembly
|
|
104
|
+
// after, and "-" is not a name
|
|
105
|
+
if (isValidJSON(argsSequence) || argsSequence === STDIN_ARG) {
|
|
106
|
+
throw new Error('Must provide --name when using custom inline JSON sequence');
|
|
107
|
+
}
|
|
108
|
+
name = path.basename(argsSequence, '.json');
|
|
50
109
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const operations = {
|
|
54
|
-
copy: copyFile,
|
|
55
|
-
symlink: (src, dest) => symlink(path.resolve(src), dest),
|
|
56
|
-
move: rename,
|
|
57
|
-
};
|
|
58
|
-
const operation = operations[load];
|
|
59
|
-
if (operation) {
|
|
60
|
-
await Promise.all(validPaths.map(filePath => operation(filePath, path.join(destDir, path.basename(filePath)))));
|
|
61
|
-
return true;
|
|
110
|
+
if (!adapter.type) {
|
|
111
|
+
throw new Error(`No "type" specified in sequence adapter "${JSON.stringify(adapter)}"`);
|
|
62
112
|
}
|
|
63
|
-
return
|
|
113
|
+
return { adapter, name };
|
|
64
114
|
}
|
|
65
|
-
export async function getAssembly({ runFlags, argsSequence,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
type = guessSequenceType(argsSequence);
|
|
80
|
-
debug(`No type specified, guessing type: ${type}`);
|
|
81
|
-
}
|
|
82
|
-
if (name) {
|
|
83
|
-
debug(`Name is: ${name}`);
|
|
84
|
-
}
|
|
85
|
-
switch (type) {
|
|
86
|
-
case 'indexedFasta': {
|
|
87
|
-
const { skipCheck, force, load, faiLocation } = runFlags;
|
|
88
|
-
let sequenceLocation = await resolveFileLocation(argsSequence, !(skipCheck || force), load === 'inPlace');
|
|
89
|
-
debug(`FASTA location resolved to: ${sequenceLocation}`);
|
|
90
|
-
let indexLocation = await resolveFileLocation(faiLocation || `${argsSequence}.fai`, !(skipCheck || force), load === 'inPlace');
|
|
91
|
-
debug(`FASTA index location resolved to: ${indexLocation}`);
|
|
92
|
-
if (!name) {
|
|
93
|
-
name = path.basename(sequenceLocation, sequenceLocation.endsWith('.fasta') ? '.fasta' : '.fa');
|
|
94
|
-
debug(`Guessing name: ${name}`);
|
|
95
|
-
}
|
|
96
|
-
const loaded = load
|
|
97
|
-
? await loadData({
|
|
98
|
-
load,
|
|
99
|
-
filePaths: [sequenceLocation, indexLocation],
|
|
100
|
-
destination: target,
|
|
101
|
-
})
|
|
102
|
-
: false;
|
|
103
|
-
if (loaded) {
|
|
104
|
-
sequenceLocation = path.basename(sequenceLocation);
|
|
105
|
-
indexLocation = path.basename(indexLocation);
|
|
106
|
-
}
|
|
107
|
-
sequence = {
|
|
108
|
-
type: 'ReferenceSequenceTrack',
|
|
109
|
-
trackId: `${name}-ReferenceSequenceTrack`,
|
|
110
|
-
adapter: {
|
|
111
|
-
type: 'IndexedFastaAdapter',
|
|
112
|
-
fastaLocation: {
|
|
113
|
-
uri: sequenceLocation,
|
|
114
|
-
locationType: 'UriLocation',
|
|
115
|
-
},
|
|
116
|
-
faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
case 'bgzipFasta': {
|
|
122
|
-
let sequenceLocation = await resolveFileLocation(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
123
|
-
debug(`compressed FASTA location resolved to: ${sequenceLocation}`);
|
|
124
|
-
let indexLocation = await resolveFileLocation(runFlags.faiLocation || `${sequenceLocation}.fai`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
125
|
-
debug(`compressed FASTA index location resolved to: ${indexLocation}`);
|
|
126
|
-
let bgzipIndexLocation = await resolveFileLocation(runFlags.gziLocation || `${sequenceLocation}.gzi`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
127
|
-
debug(`bgzip index location resolved to: ${bgzipIndexLocation}`);
|
|
128
|
-
if (!name) {
|
|
129
|
-
name = path.basename(sequenceLocation, sequenceLocation.endsWith('.fasta.gz') ? '.fasta.gz' : '.fa.gz');
|
|
130
|
-
debug(`Guessing name: ${name}`);
|
|
131
|
-
}
|
|
132
|
-
const loaded = runFlags.load
|
|
133
|
-
? await loadData({
|
|
134
|
-
load: runFlags.load,
|
|
135
|
-
filePaths: [sequenceLocation, indexLocation, bgzipIndexLocation],
|
|
136
|
-
destination: target,
|
|
137
|
-
})
|
|
138
|
-
: false;
|
|
139
|
-
if (loaded) {
|
|
140
|
-
sequenceLocation = path.basename(sequenceLocation);
|
|
141
|
-
indexLocation = path.basename(indexLocation);
|
|
142
|
-
bgzipIndexLocation = path.basename(bgzipIndexLocation);
|
|
143
|
-
}
|
|
144
|
-
sequence = {
|
|
145
|
-
type: 'ReferenceSequenceTrack',
|
|
146
|
-
trackId: `${name}-ReferenceSequenceTrack`,
|
|
147
|
-
adapter: {
|
|
148
|
-
type: 'BgzipFastaAdapter',
|
|
149
|
-
fastaLocation: {
|
|
150
|
-
uri: sequenceLocation,
|
|
151
|
-
locationType: 'UriLocation',
|
|
152
|
-
},
|
|
153
|
-
faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
|
|
154
|
-
gziLocation: {
|
|
155
|
-
uri: bgzipIndexLocation,
|
|
156
|
-
locationType: 'UriLocation',
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
case 'twoBit': {
|
|
163
|
-
let sequenceLocation = await resolveFileLocation(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
164
|
-
debug(`2bit location resolved to: ${sequenceLocation}`);
|
|
165
|
-
if (!name) {
|
|
166
|
-
name = path.basename(sequenceLocation, '.2bit');
|
|
167
|
-
debug(`Guessing name: ${name}`);
|
|
168
|
-
}
|
|
169
|
-
const loaded = runFlags.load
|
|
170
|
-
? await loadData({
|
|
171
|
-
load: runFlags.load,
|
|
172
|
-
filePaths: [sequenceLocation],
|
|
173
|
-
destination: target,
|
|
174
|
-
})
|
|
175
|
-
: false;
|
|
176
|
-
if (loaded) {
|
|
177
|
-
sequenceLocation = path.basename(sequenceLocation);
|
|
178
|
-
}
|
|
179
|
-
sequence = {
|
|
180
|
-
type: 'ReferenceSequenceTrack',
|
|
181
|
-
trackId: `${name}-ReferenceSequenceTrack`,
|
|
182
|
-
adapter: {
|
|
183
|
-
type: 'TwoBitAdapter',
|
|
184
|
-
twoBitLocation: {
|
|
185
|
-
uri: sequenceLocation,
|
|
186
|
-
locationType: 'UriLocation',
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
case 'chromSizes': {
|
|
193
|
-
let sequenceLocation = await resolveFileLocation(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
194
|
-
debug(`chrom.sizes location resolved to: ${sequenceLocation}`);
|
|
195
|
-
if (!name) {
|
|
196
|
-
name = path.basename(sequenceLocation, '.chrom.sizes');
|
|
197
|
-
debug(`Guessing name: ${name}`);
|
|
198
|
-
}
|
|
199
|
-
const loaded = runFlags.load
|
|
200
|
-
? await loadData({
|
|
201
|
-
load: runFlags.load,
|
|
202
|
-
filePaths: [sequenceLocation],
|
|
203
|
-
destination: target,
|
|
204
|
-
})
|
|
205
|
-
: false;
|
|
206
|
-
if (loaded) {
|
|
207
|
-
sequenceLocation = path.basename(sequenceLocation);
|
|
208
|
-
}
|
|
209
|
-
sequence = {
|
|
210
|
-
type: 'ReferenceSequenceTrack',
|
|
211
|
-
trackId: `${name}-ReferenceSequenceTrack`,
|
|
212
|
-
adapter: {
|
|
213
|
-
type: 'ChromSizesAdapter',
|
|
214
|
-
chromSizesLocation: {
|
|
215
|
-
uri: sequenceLocation,
|
|
216
|
-
locationType: 'UriLocation',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
};
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
case 'custom': {
|
|
223
|
-
const adapter = await readInlineOrFileJson(argsSequence);
|
|
224
|
-
debug(`Custom adapter: ${JSON.stringify(adapter)}`);
|
|
225
|
-
if (!name) {
|
|
226
|
-
if (isValidJSON(argsSequence)) {
|
|
227
|
-
throw new Error('Must provide --name when using custom inline JSON sequence');
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
name = path.basename(argsSequence, '.json');
|
|
231
|
-
}
|
|
232
|
-
debug(`Guessing name: ${name}`);
|
|
233
|
-
}
|
|
234
|
-
if (!('type' in adapter)) {
|
|
235
|
-
throw new Error(`No "type" specified in sequence adapter "${JSON.stringify(adapter)}"`);
|
|
236
|
-
}
|
|
237
|
-
sequence = {
|
|
238
|
-
type: 'ReferenceSequenceTrack',
|
|
239
|
-
trackId: `${name}-ReferenceSequenceTrack`,
|
|
240
|
-
adapter,
|
|
241
|
-
};
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
115
|
+
export async function getAssembly({ runFlags, argsSequence, }) {
|
|
116
|
+
validateLoadAndLocation(argsSequence, runFlags.load);
|
|
117
|
+
const type = runFlags.type ?? guessSequenceType(argsSequence);
|
|
118
|
+
debug(runFlags.type ? `Type is: ${type}` : `No type specified, guessed: ${type}`);
|
|
119
|
+
const uri = (p) => ({
|
|
120
|
+
uri: mapLocationForFiles(p, runFlags.load),
|
|
121
|
+
locationType: 'UriLocation',
|
|
122
|
+
});
|
|
123
|
+
if (type === 'custom') {
|
|
124
|
+
const { adapter, name } = await customSequenceAdapter(argsSequence, runFlags.name);
|
|
125
|
+
return {
|
|
126
|
+
assembly: { name, sequence: makeSequence(name, adapter) },
|
|
127
|
+
filesToLoad: [],
|
|
128
|
+
};
|
|
244
129
|
}
|
|
245
|
-
|
|
130
|
+
const spec = seqSpecs[type];
|
|
131
|
+
const sidecars = spec.sidecars.map(s => ({
|
|
132
|
+
field: s.field,
|
|
133
|
+
path: runFlags[s.field] ?? `${argsSequence}${s.suffix}`,
|
|
134
|
+
}));
|
|
135
|
+
const name = runFlags.name || spec.defaultName(argsSequence);
|
|
136
|
+
const adapter = {
|
|
137
|
+
type: spec.adapterType,
|
|
138
|
+
[spec.locField]: uri(argsSequence),
|
|
139
|
+
...Object.fromEntries(sidecars.map(s => [s.field, uri(s.path)])),
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
assembly: { name, sequence: makeSequence(name, adapter) },
|
|
143
|
+
filesToLoad: [argsSequence, ...sidecars.map(s => s.path)],
|
|
144
|
+
};
|
|
246
145
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
146
|
+
function makeSequence(name, adapter) {
|
|
147
|
+
return {
|
|
148
|
+
type: 'ReferenceSequenceTrack',
|
|
149
|
+
trackId: `${name}-ReferenceSequenceTrack`,
|
|
150
|
+
adapter,
|
|
151
|
+
};
|
|
255
152
|
}
|
|
256
153
|
export async function resolveTargetPath(output) {
|
|
257
|
-
|
|
154
|
+
const stat = await ignoreNotFound(fs.promises.stat(output));
|
|
155
|
+
if (!stat) {
|
|
258
156
|
const dir = output.endsWith('.json') ? path.dirname(output) : output;
|
|
259
157
|
await fs.promises.mkdir(dir, { recursive: true });
|
|
260
158
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
isDir = fs.statSync(output).isDirectory();
|
|
264
|
-
}
|
|
265
|
-
catch (e) { }
|
|
266
|
-
return isDir ? path.join(output, 'config.json') : output;
|
|
159
|
+
const finalStat = stat ?? (await ignoreNotFound(fs.promises.stat(output)));
|
|
160
|
+
return finalStat?.isDirectory() ? path.join(output, 'config.json') : output;
|
|
267
161
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
162
|
+
async function resolveRefNameAliasAdapter(refNameAliases, runFlags) {
|
|
163
|
+
if (runFlags.refNameAliasesType === 'custom') {
|
|
164
|
+
const config = await readInlineOrFileJson(refNameAliases);
|
|
165
|
+
if (!config.type) {
|
|
166
|
+
throw new Error(`No "type" specified in refNameAliases adapter "${JSON.stringify(config)}"`);
|
|
167
|
+
}
|
|
168
|
+
debug(`Adding custom refNameAliases config: ${JSON.stringify(config)}`);
|
|
169
|
+
return config;
|
|
273
170
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
171
|
+
// rewriting the path to a bare basename is only right when a load mode is
|
|
172
|
+
// going to copy the file next to config.json. With no --load — which a URL
|
|
173
|
+
// sequence forces, since --load with a URL is an error — the file stays where
|
|
174
|
+
// the user pointed at it, rather than the config naming a basename that
|
|
175
|
+
// nothing ever put there
|
|
176
|
+
const location = mapLocationForFiles(refNameAliases, runFlags.load ?? 'inPlace');
|
|
177
|
+
debug(`refName aliases file location: ${location}`);
|
|
178
|
+
return {
|
|
179
|
+
type: 'RefNameAliasAdapter',
|
|
180
|
+
location: { uri: location, locationType: 'UriLocation' },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// --config is merged shallowly, except for `sequence`: the generated
|
|
184
|
+
// ReferenceSequenceTrack already holds the adapter built from the sequence
|
|
185
|
+
// file, so a `sequence` hunk layers onto it (e.g. formatAbout) rather than
|
|
186
|
+
// replacing it and dropping the adapter
|
|
187
|
+
function applyExtraConfig(assembly, config) {
|
|
188
|
+
if (config === undefined) {
|
|
189
|
+
return assembly;
|
|
278
190
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const refNameAliasesConfig = await readInlineOrFileJson(runFlags.refNameAliases);
|
|
283
|
-
if (!refNameAliasesConfig.type) {
|
|
284
|
-
throw new Error(`No "type" specified in refNameAliases adapter "${JSON.stringify(refNameAliasesConfig)}"`);
|
|
285
|
-
}
|
|
286
|
-
debug(`Adding custom refNameAliases config: ${JSON.stringify(refNameAliasesConfig)}`);
|
|
287
|
-
enhancedAssembly.refNameAliases = {
|
|
288
|
-
adapter: refNameAliasesConfig,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
const refNameAliasesLocation = await resolveFileLocation(runFlags.refNameAliases, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
|
|
293
|
-
debug(`refName aliases file location resolved to: ${refNameAliasesLocation}`);
|
|
294
|
-
enhancedAssembly.refNameAliases = {
|
|
295
|
-
adapter: {
|
|
296
|
-
type: 'RefNameAliasAdapter',
|
|
297
|
-
location: {
|
|
298
|
-
uri: refNameAliasesLocation,
|
|
299
|
-
locationType: 'UriLocation',
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
};
|
|
303
|
-
}
|
|
191
|
+
const { sequence, name, ...rest } = parseConfigFlag(config);
|
|
192
|
+
if (sequence !== undefined && !isRecord(sequence)) {
|
|
193
|
+
throw new Error('--config "sequence" must be an object');
|
|
304
194
|
}
|
|
305
|
-
if (
|
|
306
|
-
|
|
195
|
+
if (name !== undefined) {
|
|
196
|
+
throw new Error('use --name rather than "name" in --config');
|
|
307
197
|
}
|
|
308
|
-
return
|
|
198
|
+
return {
|
|
199
|
+
...assembly,
|
|
200
|
+
...rest,
|
|
201
|
+
name: assembly.name,
|
|
202
|
+
sequence: { ...assembly.sequence, ...sequence },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export async function enhanceAssembly(assembly, runFlags) {
|
|
206
|
+
const { refNameAliases, refNameAliasesType } = runFlags;
|
|
207
|
+
// a non-custom refNameAliases points at a local aliases file; under a --load
|
|
208
|
+
// mode its location is rewritten to a bare basename, so the file itself must
|
|
209
|
+
// be loaded into the config dir alongside the sequence. Custom aliases are
|
|
210
|
+
// embedded inline (no file), and URLs are referenced in place.
|
|
211
|
+
const filesToLoad = refNameAliases && refNameAliasesType !== 'custom' && !isURL(refNameAliases)
|
|
212
|
+
? [refNameAliases]
|
|
213
|
+
: [];
|
|
214
|
+
// typed flags layer over --config, so the most specific flag a user reaches
|
|
215
|
+
// for wins
|
|
216
|
+
return {
|
|
217
|
+
assembly: {
|
|
218
|
+
...applyExtraConfig(assembly, runFlags.config),
|
|
219
|
+
...(runFlags.alias?.length && { aliases: runFlags.alias }),
|
|
220
|
+
...(runFlags.refNameColors && {
|
|
221
|
+
refNameColors: parseCommaSeparatedString(runFlags.refNameColors),
|
|
222
|
+
}),
|
|
223
|
+
...(runFlags.displayName && { displayName: runFlags.displayName }),
|
|
224
|
+
...(refNameAliases && {
|
|
225
|
+
refNameAliases: {
|
|
226
|
+
adapter: await resolveRefNameAliasAdapter(refNameAliases, runFlags),
|
|
227
|
+
},
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
filesToLoad,
|
|
231
|
+
};
|
|
309
232
|
}
|
|
310
233
|
export function createDefaultConfig() {
|
|
311
234
|
return {
|
|
@@ -334,11 +257,11 @@ export async function loadOrCreateConfig(target) {
|
|
|
334
257
|
}
|
|
335
258
|
export async function addAssemblyToConfig({ config, assembly, runFlags, }) {
|
|
336
259
|
const { updatedItems, wasOverwritten } = findAndUpdateOrAdd({
|
|
337
|
-
items: config.assemblies
|
|
260
|
+
items: config.assemblies ?? [],
|
|
338
261
|
newItem: assembly,
|
|
339
262
|
idField: 'name',
|
|
340
263
|
getId: item => item.name,
|
|
341
|
-
|
|
264
|
+
force: runFlags.force ?? false,
|
|
342
265
|
itemType: 'assembly',
|
|
343
266
|
});
|
|
344
267
|
return {
|
|
@@ -346,12 +269,3 @@ export async function addAssemblyToConfig({ config, assembly, runFlags, }) {
|
|
|
346
269
|
wasOverwritten,
|
|
347
270
|
};
|
|
348
271
|
}
|
|
349
|
-
export async function saveConfigAndReport({ config, target, assembly, wasOverwritten, }) {
|
|
350
|
-
await saveConfigAndReportBase({
|
|
351
|
-
config,
|
|
352
|
-
target,
|
|
353
|
-
itemType: 'assembly',
|
|
354
|
-
itemName: assembly.name,
|
|
355
|
-
wasOverwritten,
|
|
356
|
-
});
|
|
357
|
-
}
|