@jbrowse/cli 3.5.1 → 3.6.1

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 (72) hide show
  1. package/README.md +291 -607
  2. package/bin/run +1 -6
  3. package/bundle/index.js +4459 -0
  4. package/dist/base.js +5 -0
  5. package/dist/bin.js +3 -0
  6. package/dist/commands/add-assembly.js +143 -0
  7. package/dist/commands/add-connection.js +177 -0
  8. package/dist/commands/add-track-json.js +81 -0
  9. package/dist/commands/add-track-utils/adapter-utils.js +304 -0
  10. package/dist/commands/add-track-utils/file-operations.js +36 -0
  11. package/dist/commands/add-track-utils/track-config.js +63 -0
  12. package/dist/commands/add-track-utils/validators.js +74 -0
  13. package/dist/commands/add-track.js +193 -0
  14. package/dist/commands/admin-server-utils.js +238 -0
  15. package/dist/commands/admin-server.js +51 -0
  16. package/dist/commands/assembly-utils.js +410 -0
  17. package/dist/commands/create.js +121 -0
  18. package/dist/commands/make-pif-utils/cigar-utils.js +29 -0
  19. package/dist/commands/make-pif-utils/file-utils.js +38 -0
  20. package/dist/commands/make-pif-utils/pif-generator.js +64 -0
  21. package/dist/commands/make-pif-utils/validators.js +22 -0
  22. package/dist/commands/make-pif.js +58 -0
  23. package/dist/commands/remove-track.js +58 -0
  24. package/dist/commands/set-default-session.js +104 -0
  25. package/dist/commands/sort-bed-utils/constants.js +12 -0
  26. package/dist/commands/sort-bed-utils/process-utils.js +23 -0
  27. package/dist/commands/sort-bed-utils/sort-utils.js +24 -0
  28. package/dist/commands/sort-bed-utils/validators.js +22 -0
  29. package/dist/commands/sort-bed.js +49 -0
  30. package/dist/commands/sort-gff-utils/constants.js +13 -0
  31. package/dist/commands/sort-gff-utils/process-utils.js +23 -0
  32. package/dist/commands/sort-gff-utils/sort-utils.js +55 -0
  33. package/dist/commands/sort-gff-utils/validators.js +21 -0
  34. package/dist/commands/sort-gff.js +49 -0
  35. package/dist/commands/text-index-utils/adapter-utils.js +63 -0
  36. package/dist/commands/text-index-utils/aggregate.js +87 -0
  37. package/dist/commands/text-index-utils/config-utils.js +59 -0
  38. package/dist/commands/text-index-utils/file-list.js +31 -0
  39. package/dist/commands/text-index-utils/index.js +9 -0
  40. package/dist/commands/text-index-utils/indexing-utils.js +84 -0
  41. package/dist/commands/text-index-utils/per-track.js +65 -0
  42. package/dist/commands/text-index-utils/validators.js +20 -0
  43. package/dist/commands/text-index.js +113 -0
  44. package/dist/commands/track-utils.js +85 -0
  45. package/dist/commands/upgrade.js +122 -0
  46. package/dist/index.js +119 -0
  47. package/dist/utils.js +154 -0
  48. package/package.json +13 -38
  49. package/bin/dev +0 -17
  50. package/bin/dev.cmd +0 -3
  51. package/bin/run.cmd +0 -3
  52. package/lib/base.js +0 -157
  53. package/lib/commands/add-assembly.js +0 -491
  54. package/lib/commands/add-connection.js +0 -170
  55. package/lib/commands/add-track-json.js +0 -67
  56. package/lib/commands/add-track.js +0 -564
  57. package/lib/commands/admin-server.js +0 -153
  58. package/lib/commands/create.js +0 -111
  59. package/lib/commands/make-pif.js +0 -116
  60. package/lib/commands/remove-track.js +0 -37
  61. package/lib/commands/set-default-session.js +0 -98
  62. package/lib/commands/sort-bed.js +0 -45
  63. package/lib/commands/sort-gff.js +0 -45
  64. package/lib/commands/text-index.js +0 -380
  65. package/lib/commands/upgrade.js +0 -109
  66. package/lib/index.js +0 -6
  67. package/oclif.manifest.json +0 -1169
  68. /package/{lib → dist}/fetchWithProxy.js +0 -0
  69. /package/{lib → dist}/types/common.js +0 -0
  70. /package/{lib → dist}/types/gff3Adapter.js +0 -0
  71. /package/{lib → dist}/types/vcfAdapter.js +0 -0
  72. /package/{lib → dist}/util.js +0 -0
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isValidPort = isValidPort;
7
+ exports.parsePort = parsePort;
8
+ exports.generateKey = generateKey;
9
+ exports.setupConfigFile = setupConfigFile;
10
+ exports.setupRoutes = setupRoutes;
11
+ exports.setupServer = setupServer;
12
+ exports.startServer = startServer;
13
+ const crypto_1 = __importDefault(require("crypto"));
14
+ const fs_1 = __importDefault(require("fs"));
15
+ const os_1 = __importDefault(require("os"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const cors_1 = __importDefault(require("cors"));
18
+ const express_1 = __importDefault(require("express"));
19
+ const utils_1 = require("../utils");
20
+ /**
21
+ * Validates if a port number is in the valid range
22
+ */
23
+ function isValidPort(port) {
24
+ return port > 0 && port < 65535;
25
+ }
26
+ /**
27
+ * Parses and validates a port string
28
+ */
29
+ function parsePort({ portStr, defaultPort = 9090, }) {
30
+ if (!portStr) {
31
+ return defaultPort;
32
+ }
33
+ const parsedPort = Number.parseInt(portStr, 10);
34
+ if (!isValidPort(parsedPort)) {
35
+ throw new Error(`${portStr} is not a valid port`);
36
+ }
37
+ return parsedPort;
38
+ }
39
+ /**
40
+ * Generates a random alphanumeric string to serve as admin key
41
+ */
42
+ function generateKey() {
43
+ return crypto_1.default.randomBytes(5).toString('hex');
44
+ }
45
+ /**
46
+ * Sets up the configuration file
47
+ */
48
+ async function setupConfigFile({ root = '.', } = {}) {
49
+ const output = root;
50
+ const isDir = fs_1.default.lstatSync(output).isDirectory();
51
+ const outFile = isDir ? `${output}/config.json` : output;
52
+ const baseDir = path_1.default.dirname(outFile);
53
+ if (fs_1.default.existsSync(outFile)) {
54
+ (0, utils_1.debug)(`Found existing config file ${outFile}`);
55
+ }
56
+ else {
57
+ (0, utils_1.debug)(`Creating config file ${outFile}`);
58
+ await (0, utils_1.writeJsonFile)(outFile, {
59
+ assemblies: [],
60
+ configuration: {},
61
+ connections: [],
62
+ defaultSession: {
63
+ name: 'New Session',
64
+ },
65
+ tracks: [],
66
+ });
67
+ }
68
+ return { outFile, baseDir };
69
+ }
70
+ /**
71
+ * Validates admin key and extracts config path from request
72
+ */
73
+ function validateAndExtractParams({ req, key, baseDir, outFile, }) {
74
+ const { body } = req;
75
+ // Check for adminKey in both query parameters and request body for backward compatibility
76
+ const adminKeyFromQuery = req.query.adminKey;
77
+ const adminKeyFromBody = body?.adminKey;
78
+ const adminKey = adminKeyFromBody || adminKeyFromQuery;
79
+ if (adminKey !== key) {
80
+ return { isValid: false, error: 'Invalid admin key' };
81
+ }
82
+ // Get configPath from either body or query parameters
83
+ const configPathParam = body?.configPath || req.query.config;
84
+ try {
85
+ // Normalize the config path
86
+ const configPath = configPathParam
87
+ ? path_1.default.normalize(path_1.default.join(baseDir, configPathParam))
88
+ : outFile;
89
+ // Check for directory traversal attempts
90
+ const normalizedBaseDir = path_1.default.normalize(baseDir);
91
+ const relPath = path_1.default.relative(normalizedBaseDir, configPath);
92
+ // Ensure the config path is within the base directory and doesn't contain path traversal
93
+ if (relPath.startsWith('..') || path_1.default.isAbsolute(relPath)) {
94
+ return { isValid: false, error: 'Cannot perform directory traversal' };
95
+ }
96
+ return { isValid: true, configPath };
97
+ }
98
+ catch (error) {
99
+ return { isValid: false, error: 'Failed to validate config path' };
100
+ }
101
+ }
102
+ /**
103
+ * Sets up the API routes for the server
104
+ */
105
+ function setupRoutes({ app, baseDir, outFile, key, }) {
106
+ // Root route
107
+ app.get('/', (_req, res) => {
108
+ res.setHeader('Content-Type', 'text/plain');
109
+ res.send('JBrowse Admin Server');
110
+ });
111
+ // Update config route
112
+ app.post('/updateConfig', (req, res) => {
113
+ const { body } = req;
114
+ const config = body.config;
115
+ const validation = validateAndExtractParams({ req, key, baseDir, outFile });
116
+ if (!validation.isValid) {
117
+ res.status(401).setHeader('Content-Type', 'text/plain');
118
+ res.send(`Error: ${validation.error}`);
119
+ return;
120
+ }
121
+ // Remove adminKey from body before saving to config
122
+ if (body.adminKey) {
123
+ delete body.adminKey;
124
+ }
125
+ try {
126
+ fs_1.default.writeFileSync(validation.configPath, JSON.stringify(config, null, 2));
127
+ res.setHeader('Content-Type', 'text/plain');
128
+ res.send('Config updated successfully');
129
+ }
130
+ catch (error) {
131
+ res.status(500).setHeader('Content-Type', 'text/plain');
132
+ res.send('Error: Failed to update config');
133
+ }
134
+ });
135
+ // Get config route
136
+ app.get('/config', (req, res) => {
137
+ const validation = validateAndExtractParams({ req, key, baseDir, outFile });
138
+ if (!validation.isValid) {
139
+ res.status(401).setHeader('Content-Type', 'text/plain');
140
+ res.send(`Error: ${validation.error}`);
141
+ return;
142
+ }
143
+ try {
144
+ if (fs_1.default.existsSync(validation.configPath)) {
145
+ const config = fs_1.default.readFileSync(validation.configPath, 'utf8');
146
+ res.setHeader('Content-Type', 'text/plain');
147
+ res.send(config);
148
+ }
149
+ else {
150
+ res.status(404).setHeader('Content-Type', 'text/plain');
151
+ res.send('Error: Config file not found');
152
+ }
153
+ }
154
+ catch (error) {
155
+ console.error('Error reading config:', error);
156
+ res.status(500).setHeader('Content-Type', 'text/plain');
157
+ res.send('Error: Failed to read config');
158
+ }
159
+ });
160
+ }
161
+ /**
162
+ * Sets up the Express server with routes
163
+ */
164
+ function setupServer({ baseDir, outFile, bodySizeLimit, }) {
165
+ // Create Express application
166
+ const app = (0, express_1.default)();
167
+ // Configure middleware
168
+ app.use(express_1.default.static(baseDir));
169
+ app.use((0, cors_1.default)());
170
+ app.use(express_1.default.json({ limit: bodySizeLimit }));
171
+ // Add error handling middleware
172
+ app.use((err, _req, res, next) => {
173
+ if (err) {
174
+ console.error('Server error:', err);
175
+ res.status(500).setHeader('Content-Type', 'text/plain');
176
+ res.send('Internal Server Error');
177
+ }
178
+ else {
179
+ next();
180
+ }
181
+ });
182
+ // Generate admin key and store it
183
+ const key = generateKey();
184
+ const keyPath = path_1.default.join(os_1.default.tmpdir(), `jbrowse-admin-${key}`);
185
+ try {
186
+ fs_1.default.writeFileSync(keyPath, key);
187
+ (0, utils_1.debug)(`Admin key stored at ${keyPath}`);
188
+ }
189
+ catch (error) {
190
+ console.error(`Failed to write admin key to ${keyPath}:`, error.message);
191
+ // Continue anyway, as this is not critical
192
+ }
193
+ // Set up routes
194
+ setupRoutes({ app, baseDir, outFile, key });
195
+ return { app, key, keyPath };
196
+ }
197
+ /**
198
+ * Starts the server and sets up shutdown handlers
199
+ */
200
+ function startServer({ app, port, key, outFile, keyPath, }) {
201
+ // Start the server
202
+ const server = app.listen(port, () => {
203
+ console.log(`Admin server started on port ${port}\n\n` +
204
+ `To access the admin interface, open your browser to:\n` +
205
+ `http://localhost:${port}?adminKey=${key}\n\n` +
206
+ `Admin key: ${key}\n` +
207
+ `Config file: ${outFile}\n\n` +
208
+ `To stop the server, press Ctrl+C`);
209
+ });
210
+ // Handle server errors
211
+ server.on('error', (error) => {
212
+ if (error.code === 'EADDRINUSE') {
213
+ console.error(`Error: Port ${port} is already in use`);
214
+ }
215
+ else {
216
+ console.error('Server error:', error.message);
217
+ }
218
+ process.exit(1);
219
+ });
220
+ // Common shutdown handler
221
+ const shutdownHandler = () => {
222
+ console.log('\nShutting down admin server...');
223
+ server.close(() => {
224
+ // Clean up admin key file
225
+ try {
226
+ fs_1.default.unlinkSync(keyPath);
227
+ (0, utils_1.debug)(`Removed admin key file: ${keyPath}`);
228
+ }
229
+ catch (error) {
230
+ // Ignore errors when cleaning up
231
+ }
232
+ process.exit(0);
233
+ });
234
+ };
235
+ // Handle server shutdown
236
+ process.on('SIGINT', shutdownHandler);
237
+ process.on('SIGTERM', shutdownHandler);
238
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = run;
4
+ const util_1 = require("util");
5
+ const utils_1 = require("../utils");
6
+ const admin_server_utils_1 = require("./admin-server-utils");
7
+ async function run(args) {
8
+ const options = {
9
+ help: {
10
+ type: 'boolean',
11
+ short: 'h',
12
+ },
13
+ port: {
14
+ type: 'string',
15
+ short: 'p',
16
+ description: 'Specified port to start the server on (default: 9090)',
17
+ },
18
+ root: {
19
+ type: 'string',
20
+ description: 'Path to the root of the JB2 installation',
21
+ },
22
+ bodySizeLimit: {
23
+ type: 'string',
24
+ description: 'Size limit of the update message (default: 25mb)',
25
+ },
26
+ };
27
+ const { values: flags } = (0, util_1.parseArgs)({
28
+ args,
29
+ options,
30
+ allowPositionals: true,
31
+ });
32
+ const description = 'Start up a small admin server for JBrowse configuration';
33
+ const examples = ['$ jbrowse admin-server', '$ jbrowse admin-server -p 8888'];
34
+ if (flags.help) {
35
+ (0, utils_1.printHelp)({
36
+ description,
37
+ examples,
38
+ usage: 'jbrowse admin-server [options]',
39
+ options,
40
+ });
41
+ return;
42
+ }
43
+ const { root, bodySizeLimit = '25mb' } = flags;
44
+ const { outFile, baseDir } = await (0, admin_server_utils_1.setupConfigFile)({ root });
45
+ // Parse and validate port
46
+ const port = (0, admin_server_utils_1.parsePort)({ portStr: flags.port });
47
+ // Set up the Express server
48
+ const { app, key, keyPath } = (0, admin_server_utils_1.setupServer)({ baseDir, outFile, bodySizeLimit });
49
+ // Start the server and set up shutdown handlers
50
+ (0, admin_server_utils_1.startServer)({ app, port, key, outFile, keyPath });
51
+ }
@@ -0,0 +1,410 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isValidJSON = isValidJSON;
7
+ exports.guessSequenceType = guessSequenceType;
8
+ exports.needLoadData = needLoadData;
9
+ exports.loadData = loadData;
10
+ exports.getAssembly = getAssembly;
11
+ exports.exists = exists;
12
+ exports.resolveTargetPath = resolveTargetPath;
13
+ exports.enhanceAssembly = enhanceAssembly;
14
+ exports.createDefaultConfig = createDefaultConfig;
15
+ exports.loadOrCreateConfig = loadOrCreateConfig;
16
+ exports.addAssemblyToConfig = addAssemblyToConfig;
17
+ exports.saveConfigAndReport = saveConfigAndReport;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const utils_1 = require("../utils");
21
+ const { rename, copyFile, symlink } = fs_1.default.promises;
22
+ function isValidJSON(string) {
23
+ try {
24
+ JSON.parse(string);
25
+ return true;
26
+ }
27
+ catch (error) {
28
+ return false;
29
+ }
30
+ }
31
+ function guessSequenceType(sequence) {
32
+ if (sequence.endsWith('.fa') ||
33
+ sequence.endsWith('.fna') ||
34
+ sequence.endsWith('.fasta')) {
35
+ return 'indexedFasta';
36
+ }
37
+ if (sequence.endsWith('.fa.gz') ||
38
+ sequence.endsWith('.fna.gz') ||
39
+ sequence.endsWith('.fasta.gz')) {
40
+ return 'bgzipFasta';
41
+ }
42
+ if (sequence.endsWith('.2bit')) {
43
+ return 'twoBit';
44
+ }
45
+ if (sequence.endsWith('.chrom.sizes')) {
46
+ return 'chromSizes';
47
+ }
48
+ if (sequence.endsWith('.json')) {
49
+ return 'custom';
50
+ }
51
+ if (isValidJSON(sequence)) {
52
+ return 'custom';
53
+ }
54
+ throw new Error('Could not determine sequence type automatically, add --type to specify it');
55
+ }
56
+ function needLoadData(location) {
57
+ let locationUrl;
58
+ try {
59
+ locationUrl = new URL(location);
60
+ }
61
+ catch (error) {
62
+ // ignore
63
+ }
64
+ if (locationUrl) {
65
+ return false;
66
+ }
67
+ return true;
68
+ }
69
+ async function loadData({ load, filePaths, destination, }) {
70
+ let locationUrl;
71
+ try {
72
+ locationUrl = new URL(filePaths[0]);
73
+ }
74
+ catch (error) {
75
+ // ignore
76
+ }
77
+ if (locationUrl) {
78
+ return false;
79
+ }
80
+ switch (load) {
81
+ case 'copy': {
82
+ await Promise.all(filePaths.map(async (filePath) => {
83
+ if (!filePath) {
84
+ return undefined;
85
+ }
86
+ return copyFile(filePath, path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
87
+ }));
88
+ return true;
89
+ }
90
+ case 'symlink': {
91
+ await Promise.all(filePaths.map(async (filePath) => {
92
+ if (!filePath) {
93
+ return undefined;
94
+ }
95
+ return symlink(path_1.default.resolve(filePath), path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
96
+ }));
97
+ return true;
98
+ }
99
+ case 'move': {
100
+ await Promise.all(filePaths.map(async (filePath) => {
101
+ if (!filePath) {
102
+ return undefined;
103
+ }
104
+ return rename(filePath, path_1.default.join(path_1.default.dirname(destination), path_1.default.basename(filePath)));
105
+ }));
106
+ return true;
107
+ }
108
+ case 'inPlace': {
109
+ return false;
110
+ }
111
+ }
112
+ return false;
113
+ }
114
+ async function getAssembly({ runFlags, argsSequence, target, }) {
115
+ let sequence;
116
+ if (needLoadData(argsSequence) && !runFlags.load) {
117
+ throw new Error('Please specify the loading operation for this file with --load copy|symlink|move|inPlace');
118
+ }
119
+ else if (!needLoadData(argsSequence) && runFlags.load) {
120
+ throw new Error('URL detected with --load flag. Please rerun the function without the --load flag');
121
+ }
122
+ let { name } = runFlags;
123
+ let { type } = runFlags;
124
+ if (type) {
125
+ (0, utils_1.debug)(`Type is: ${type}`);
126
+ }
127
+ else {
128
+ type = guessSequenceType(argsSequence);
129
+ (0, utils_1.debug)(`No type specified, guessing type: ${type}`);
130
+ }
131
+ if (name) {
132
+ (0, utils_1.debug)(`Name is: ${name}`);
133
+ }
134
+ switch (type) {
135
+ case 'indexedFasta': {
136
+ const { skipCheck, force, load, faiLocation } = runFlags;
137
+ let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(skipCheck || force), load === 'inPlace');
138
+ (0, utils_1.debug)(`FASTA location resolved to: ${sequenceLocation}`);
139
+ let indexLocation = await (0, utils_1.resolveFileLocation)(faiLocation || `${argsSequence}.fai`, !(skipCheck || force), load === 'inPlace');
140
+ (0, utils_1.debug)(`FASTA index location resolved to: ${indexLocation}`);
141
+ if (!name) {
142
+ name = path_1.default.basename(sequenceLocation, sequenceLocation.endsWith('.fasta') ? '.fasta' : '.fa');
143
+ (0, utils_1.debug)(`Guessing name: ${name}`);
144
+ }
145
+ const loaded = load
146
+ ? await loadData({
147
+ load,
148
+ filePaths: [sequenceLocation, indexLocation],
149
+ destination: target,
150
+ })
151
+ : false;
152
+ if (loaded) {
153
+ sequenceLocation = path_1.default.basename(sequenceLocation);
154
+ indexLocation = path_1.default.basename(indexLocation);
155
+ }
156
+ sequence = {
157
+ type: 'ReferenceSequenceTrack',
158
+ trackId: `${name}-ReferenceSequenceTrack`,
159
+ adapter: {
160
+ type: 'IndexedFastaAdapter',
161
+ fastaLocation: {
162
+ uri: sequenceLocation,
163
+ locationType: 'UriLocation',
164
+ },
165
+ faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
166
+ },
167
+ };
168
+ break;
169
+ }
170
+ case 'bgzipFasta': {
171
+ let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
172
+ (0, utils_1.debug)(`compressed FASTA location resolved to: ${sequenceLocation}`);
173
+ let indexLocation = await (0, utils_1.resolveFileLocation)(runFlags.faiLocation || `${sequenceLocation}.fai`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
174
+ (0, utils_1.debug)(`compressed FASTA index location resolved to: ${indexLocation}`);
175
+ let bgzipIndexLocation = await (0, utils_1.resolveFileLocation)(runFlags.gziLocation || `${sequenceLocation}.gzi`, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
176
+ (0, utils_1.debug)(`bgzip index location resolved to: ${bgzipIndexLocation}`);
177
+ if (!name) {
178
+ name = path_1.default.basename(sequenceLocation, sequenceLocation.endsWith('.fasta.gz') ? '.fasta.gz' : '.fa.gz');
179
+ (0, utils_1.debug)(`Guessing name: ${name}`);
180
+ }
181
+ const loaded = runFlags.load
182
+ ? await loadData({
183
+ load: runFlags.load,
184
+ filePaths: [sequenceLocation, indexLocation, bgzipIndexLocation],
185
+ destination: target,
186
+ })
187
+ : false;
188
+ if (loaded) {
189
+ sequenceLocation = path_1.default.basename(sequenceLocation);
190
+ indexLocation = path_1.default.basename(indexLocation);
191
+ bgzipIndexLocation = path_1.default.basename(bgzipIndexLocation);
192
+ }
193
+ sequence = {
194
+ type: 'ReferenceSequenceTrack',
195
+ trackId: `${name}-ReferenceSequenceTrack`,
196
+ adapter: {
197
+ type: 'BgzipFastaAdapter',
198
+ fastaLocation: {
199
+ uri: sequenceLocation,
200
+ locationType: 'UriLocation',
201
+ },
202
+ faiLocation: { uri: indexLocation, locationType: 'UriLocation' },
203
+ gziLocation: {
204
+ uri: bgzipIndexLocation,
205
+ locationType: 'UriLocation',
206
+ },
207
+ },
208
+ };
209
+ break;
210
+ }
211
+ case 'twoBit': {
212
+ let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
213
+ (0, utils_1.debug)(`2bit location resolved to: ${sequenceLocation}`);
214
+ if (!name) {
215
+ name = path_1.default.basename(sequenceLocation, '.2bit');
216
+ (0, utils_1.debug)(`Guessing name: ${name}`);
217
+ }
218
+ const loaded = runFlags.load
219
+ ? await loadData({
220
+ load: runFlags.load,
221
+ filePaths: [sequenceLocation],
222
+ destination: target,
223
+ })
224
+ : false;
225
+ if (loaded) {
226
+ sequenceLocation = path_1.default.basename(sequenceLocation);
227
+ }
228
+ sequence = {
229
+ type: 'ReferenceSequenceTrack',
230
+ trackId: `${name}-ReferenceSequenceTrack`,
231
+ adapter: {
232
+ type: 'TwoBitAdapter',
233
+ twoBitLocation: {
234
+ uri: sequenceLocation,
235
+ locationType: 'UriLocation',
236
+ },
237
+ },
238
+ };
239
+ break;
240
+ }
241
+ case 'chromSizes': {
242
+ let sequenceLocation = await (0, utils_1.resolveFileLocation)(argsSequence, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
243
+ (0, utils_1.debug)(`chrom.sizes location resolved to: ${sequenceLocation}`);
244
+ if (!name) {
245
+ name = path_1.default.basename(sequenceLocation, '.chrom.sizes');
246
+ (0, utils_1.debug)(`Guessing name: ${name}`);
247
+ }
248
+ const loaded = runFlags.load
249
+ ? await loadData({
250
+ load: runFlags.load,
251
+ filePaths: [sequenceLocation],
252
+ destination: target,
253
+ })
254
+ : false;
255
+ if (loaded) {
256
+ sequenceLocation = path_1.default.basename(sequenceLocation);
257
+ }
258
+ sequence = {
259
+ type: 'ReferenceSequenceTrack',
260
+ trackId: `${name}-ReferenceSequenceTrack`,
261
+ adapter: {
262
+ type: 'ChromSizesAdapter',
263
+ chromSizesLocation: {
264
+ uri: sequenceLocation,
265
+ locationType: 'UriLocation',
266
+ },
267
+ },
268
+ };
269
+ break;
270
+ }
271
+ case 'custom': {
272
+ const adapter = await (0, utils_1.readInlineOrFileJson)(argsSequence);
273
+ (0, utils_1.debug)(`Custom adapter: ${JSON.stringify(adapter)}`);
274
+ if (!name) {
275
+ if (isValidJSON(argsSequence)) {
276
+ throw new Error('Must provide --name when using custom inline JSON sequence');
277
+ }
278
+ else {
279
+ name = path_1.default.basename(argsSequence, '.json');
280
+ }
281
+ (0, utils_1.debug)(`Guessing name: ${name}`);
282
+ }
283
+ if (!('type' in adapter)) {
284
+ throw new Error(`No "type" specified in sequence adapter "${JSON.stringify(adapter)}"`);
285
+ }
286
+ sequence = {
287
+ type: 'ReferenceSequenceTrack',
288
+ trackId: `${name}-ReferenceSequenceTrack`,
289
+ adapter,
290
+ };
291
+ break;
292
+ }
293
+ }
294
+ return { name, sequence };
295
+ }
296
+ async function exists(s) {
297
+ try {
298
+ await fs_1.default.promises.access(s, fs_1.default.constants.F_OK);
299
+ return true;
300
+ }
301
+ catch {
302
+ return false;
303
+ }
304
+ }
305
+ async function resolveTargetPath(output) {
306
+ if (!(await exists(output))) {
307
+ const dir = output.endsWith('.json') ? path_1.default.dirname(output) : output;
308
+ await fs_1.default.promises.mkdir(dir, { recursive: true });
309
+ }
310
+ let isDir = false;
311
+ try {
312
+ isDir = fs_1.default.statSync(output).isDirectory();
313
+ }
314
+ catch (e) { }
315
+ return isDir ? path_1.default.join(output, 'config.json') : output;
316
+ }
317
+ async function enhanceAssembly(assembly, runFlags) {
318
+ const enhancedAssembly = { ...assembly };
319
+ if (runFlags.alias?.length) {
320
+ (0, utils_1.debug)(`Adding assembly aliases: ${runFlags.alias}`);
321
+ enhancedAssembly.aliases = runFlags.alias;
322
+ }
323
+ if (runFlags.refNameColors) {
324
+ enhancedAssembly.refNameColors = runFlags.refNameColors
325
+ .split(',')
326
+ .map((color) => color.trim());
327
+ }
328
+ if (runFlags.refNameAliases) {
329
+ if (runFlags.refNameAliasesType &&
330
+ runFlags.refNameAliasesType === 'custom') {
331
+ const refNameAliasesConfig = await (0, utils_1.readInlineOrFileJson)(runFlags.refNameAliases);
332
+ if (!refNameAliasesConfig.type) {
333
+ throw new Error(`No "type" specified in refNameAliases adapter "${JSON.stringify(refNameAliasesConfig)}"`);
334
+ }
335
+ (0, utils_1.debug)(`Adding custom refNameAliases config: ${JSON.stringify(refNameAliasesConfig)}`);
336
+ enhancedAssembly.refNameAliases = {
337
+ adapter: refNameAliasesConfig,
338
+ };
339
+ }
340
+ else {
341
+ const refNameAliasesLocation = await (0, utils_1.resolveFileLocation)(runFlags.refNameAliases, !(runFlags.skipCheck || runFlags.force), runFlags.load === 'inPlace');
342
+ (0, utils_1.debug)(`refName aliases file location resolved to: ${refNameAliasesLocation}`);
343
+ enhancedAssembly.refNameAliases = {
344
+ adapter: {
345
+ type: 'RefNameAliasAdapter',
346
+ location: {
347
+ uri: refNameAliasesLocation,
348
+ locationType: 'UriLocation',
349
+ },
350
+ },
351
+ };
352
+ }
353
+ }
354
+ if (runFlags.displayName) {
355
+ enhancedAssembly.displayName = runFlags.displayName;
356
+ }
357
+ return enhancedAssembly;
358
+ }
359
+ function createDefaultConfig() {
360
+ return {
361
+ assemblies: [],
362
+ configuration: {},
363
+ connections: [],
364
+ defaultSession: {
365
+ name: 'New Session',
366
+ },
367
+ tracks: [],
368
+ };
369
+ }
370
+ async function loadOrCreateConfig(target) {
371
+ const defaultConfig = createDefaultConfig();
372
+ if (fs_1.default.existsSync(target)) {
373
+ (0, utils_1.debug)(`Found existing config file ${target}`);
374
+ return {
375
+ ...defaultConfig,
376
+ ...(await (0, utils_1.readJsonFile)(target)),
377
+ };
378
+ }
379
+ else {
380
+ (0, utils_1.debug)(`Creating config file ${target}`);
381
+ return { ...defaultConfig };
382
+ }
383
+ }
384
+ async function addAssemblyToConfig({ config, assembly, runFlags, }) {
385
+ const updatedConfig = { ...config };
386
+ if (!updatedConfig.assemblies) {
387
+ updatedConfig.assemblies = [];
388
+ }
389
+ const idx = updatedConfig.assemblies.findIndex(configAssembly => configAssembly.name === assembly.name);
390
+ if (idx !== -1) {
391
+ (0, utils_1.debug)(`Found existing assembly ${assembly.name} in configuration`);
392
+ if (runFlags.overwrite || runFlags.force) {
393
+ (0, utils_1.debug)(`Overwriting assembly ${assembly.name} in configuration`);
394
+ updatedConfig.assemblies[idx] = assembly;
395
+ return { config: updatedConfig, wasOverwritten: true };
396
+ }
397
+ else {
398
+ throw new Error(`Cannot add assembly with name ${assembly.name}, an assembly with that name already exists`);
399
+ }
400
+ }
401
+ else {
402
+ updatedConfig.assemblies.push(assembly);
403
+ return { config: updatedConfig, wasOverwritten: false };
404
+ }
405
+ }
406
+ async function saveConfigAndReport({ config, target, assembly, wasOverwritten, }) {
407
+ (0, utils_1.debug)(`Writing configuration to file ${target}`);
408
+ await (0, utils_1.writeJsonFile)(target, config);
409
+ console.log(`${wasOverwritten ? 'Overwrote' : 'Added'} assembly "${assembly.name}" ${wasOverwritten ? 'in' : 'to'} ${target}`);
410
+ }