@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.
Files changed (71) hide show
  1. package/README.md +451 -72
  2. package/dist/base.js +0 -3
  3. package/dist/bin.js +2 -1
  4. package/dist/cliFetch.js +3 -0
  5. package/dist/commands/add-assembly/index.js +46 -19
  6. package/dist/commands/add-assembly/utils.js +204 -290
  7. package/dist/commands/add-connection.js +73 -79
  8. package/dist/commands/add-track-json.js +24 -12
  9. package/dist/commands/add-track-utils/adapter-utils.js +189 -288
  10. package/dist/commands/add-track-utils/file-operations.js +27 -11
  11. package/dist/commands/add-track-utils/multiwig.js +47 -0
  12. package/dist/commands/add-track-utils/track-config.js +66 -25
  13. package/dist/commands/add-track-utils/validators.js +36 -20
  14. package/dist/commands/add-track.js +200 -62
  15. package/dist/commands/admin-server/index.js +39 -50
  16. package/dist/commands/admin-server/utils.js +238 -126
  17. package/dist/commands/create.js +18 -34
  18. package/dist/commands/make-density/density-generator.js +180 -0
  19. package/dist/commands/make-density/index.js +139 -0
  20. package/dist/commands/make-pif/index.js +133 -11
  21. package/dist/commands/make-pif/pif-generator.js +243 -43
  22. package/dist/commands/process-utils.js +13 -2
  23. package/dist/commands/remove-track.js +20 -13
  24. package/dist/commands/set-default-session.js +35 -31
  25. package/dist/commands/shared/config-operations.js +3 -10
  26. package/dist/commands/shared/density.js +12 -0
  27. package/dist/commands/shared/sort-utils.js +67 -10
  28. package/dist/commands/shared/validators.js +38 -3
  29. package/dist/commands/sort-bed.js +2 -33
  30. package/dist/commands/sort-gff.js +2 -33
  31. package/dist/commands/text-index/adapter-utils.js +19 -26
  32. package/dist/commands/text-index/aggregate.js +40 -34
  33. package/dist/commands/text-index/command.js +37 -7
  34. package/dist/commands/text-index/config-utils.js +62 -85
  35. package/dist/commands/text-index/file-list.js +32 -18
  36. package/dist/commands/text-index/index.js +4 -4
  37. package/dist/commands/text-index/indexing-utils.js +44 -96
  38. package/dist/commands/text-index/per-track.js +44 -31
  39. package/dist/commands/text-index/validators.js +1 -1
  40. package/dist/commands/upgrade.js +20 -32
  41. package/dist/commands/validate/configManifest.generated.js +7613 -0
  42. package/dist/commands/validate/configSchema.generated.js +20400 -0
  43. package/dist/commands/validate/displayDefaultKeys.js +28 -0
  44. package/dist/commands/validate/index.js +113 -0
  45. package/dist/commands/validate/liftConfig.js +34 -0
  46. package/dist/commands/validate/markRules/aggregateFieldName.js +9 -0
  47. package/dist/commands/validate/markRules/autoBin.js +27 -0
  48. package/dist/commands/validate/markRules/colorScale.js +244 -0
  49. package/dist/commands/validate/markRules/cssColorsLevel4.js +162 -0
  50. package/dist/commands/validate/markRules/jexlStrings.js +23 -0
  51. package/dist/commands/validate/markRules/markProblems.js +532 -0
  52. package/dist/commands/validate/markRules/markRuleFacts.js +6 -0
  53. package/dist/commands/validate/markRules/markSpecs.js +96 -0
  54. package/dist/commands/validate/markRules/markVocabulary.js +57 -0
  55. package/dist/commands/validate/markRules/shapeNameList.js +7 -0
  56. package/dist/commands/validate/markRules/stepChannels.js +55 -0
  57. package/dist/commands/validate/schemaValidate.js +425 -0
  58. package/dist/commands/validate/suggest.js +35 -0
  59. package/dist/commands/validate/types.js +6 -0
  60. package/dist/commands/validate/validateConfig.js +502 -0
  61. package/dist/index.js +104 -50
  62. package/dist/types/common.js +3 -27
  63. package/dist/utils.js +309 -128
  64. package/dist/version.js +1 -1
  65. package/package.json +8 -14
  66. package/bin/run +0 -4
  67. package/bundle/index.js +0 -3686
  68. package/dist/commands/make-pif/cigar-utils.js +0 -24
  69. package/dist/commands/make-pif/file-utils.js +0 -30
  70. package/dist/commands/track-utils.js +0 -74
  71. package/dist/util.js +0 -35
@@ -1,10 +1,35 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { isURL } from "../../types/common.js";
4
- import { debug, readInlineOrFileJson, readJsonFile, resolveFileLocation, } from "../../utils.js";
5
- import { findAndUpdateOrAdd, saveConfigAndReport as saveConfigAndReportBase, } from "../shared/config-operations.js";
6
- const { rename, copyFile, symlink } = fs.promises;
7
- export function isValidJSON(string) {
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
- export function guessSequenceType(sequence) {
17
- if (sequence.endsWith('.fa') ||
18
- sequence.endsWith('.fna') ||
19
- sequence.endsWith('.fasta')) {
20
- return 'indexedFasta';
21
- }
22
- if (sequence.endsWith('.fa.gz') ||
23
- sequence.endsWith('.fna.gz') ||
24
- sequence.endsWith('.fasta.gz')) {
25
- return 'bgzipFasta';
26
- }
27
- if (sequence.endsWith('.2bit')) {
28
- return 'twoBit';
29
- }
30
- if (sequence.endsWith('.chrom.sizes')) {
31
- return 'chromSizes';
32
- }
33
- if (sequence.endsWith('.json')) {
34
- return 'custom';
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
- export function needLoadData(location) {
42
- return !isURL(location);
43
- }
44
- export async function loadData({ load, filePaths, destination, }) {
45
- if (isURL(filePaths[0])) {
46
- return false;
47
- }
48
- if (load === 'inPlace') {
49
- return false;
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
- const destDir = path.dirname(destination);
52
- const validPaths = filePaths.filter(f => !!f);
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 false;
113
+ return { adapter, name };
64
114
  }
65
- export async function getAssembly({ runFlags, argsSequence, target, }) {
66
- let sequence;
67
- if (needLoadData(argsSequence) && !runFlags.load) {
68
- throw new Error('Please specify the loading operation for this file with --load copy|symlink|move|inPlace');
69
- }
70
- else if (!needLoadData(argsSequence) && runFlags.load) {
71
- throw new Error('URL detected with --load flag. Please rerun the function without the --load flag');
72
- }
73
- let { name } = runFlags;
74
- let { type } = runFlags;
75
- if (type) {
76
- debug(`Type is: ${type}`);
77
- }
78
- else {
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
- return { name, sequence };
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
- export async function exists(s) {
248
- try {
249
- await fs.promises.access(s, fs.constants.F_OK);
250
- return true;
251
- }
252
- catch {
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
- if (!(await exists(output))) {
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
- let isDir = false;
262
- try {
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
- export async function enhanceAssembly(assembly, runFlags) {
269
- const enhancedAssembly = { ...assembly };
270
- if (runFlags.alias?.length) {
271
- debug(`Adding assembly aliases: ${runFlags.alias}`);
272
- enhancedAssembly.aliases = runFlags.alias;
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
- if (runFlags.refNameColors) {
275
- enhancedAssembly.refNameColors = runFlags.refNameColors
276
- .split(',')
277
- .map((color) => color.trim());
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
- if (runFlags.refNameAliases) {
280
- if (runFlags.refNameAliasesType &&
281
- runFlags.refNameAliasesType === 'custom') {
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 (runFlags.displayName) {
306
- enhancedAssembly.displayName = runFlags.displayName;
195
+ if (name !== undefined) {
196
+ throw new Error('use --name rather than "name" in --config');
307
197
  }
308
- return enhancedAssembly;
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
- allowOverwrite: runFlags.overwrite || runFlags.force || false,
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
- }