react-msaview-cli 5.0.6 → 5.0.13
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/package.json +13 -5
- package/src/docker-runner.ts +103 -0
- package/src/ebi-api.ts +13 -28
- package/src/index.ts +30 -6
- package/src/interproscan-msa.ts +21 -3
- package/src/local-runner.ts +3 -7
- package/src/singularity-runner.ts +104 -0
- package/src/util.ts +3 -0
package/package.json
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-msaview-cli",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/GMOD/JBrowseMSA.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
6
11
|
"bin": {
|
|
7
12
|
"react-msaview-cli": "./dist/index.js"
|
|
8
13
|
},
|
|
9
|
-
"main": "dist/index.js",
|
|
10
|
-
"types": "dist/index.d.ts",
|
|
11
14
|
"exports": {
|
|
12
15
|
".": {
|
|
13
16
|
"types": "./dist/index.d.ts",
|
|
14
17
|
"default": "./dist/index.js"
|
|
15
18
|
}
|
|
16
19
|
},
|
|
20
|
+
"types": "dist/index.d.ts",
|
|
17
21
|
"files": [
|
|
18
22
|
"dist",
|
|
19
23
|
"src"
|
|
20
24
|
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^25.6.0"
|
|
27
|
+
},
|
|
21
28
|
"dependencies": {
|
|
22
|
-
"msa-parsers": "5.0.
|
|
29
|
+
"msa-parsers": "5.0.13"
|
|
23
30
|
},
|
|
24
31
|
"scripts": {
|
|
25
32
|
"clean": "rimraf dist",
|
|
26
33
|
"build": "tsc",
|
|
27
34
|
"watch": "tsc --watch",
|
|
28
35
|
"test": "vitest"
|
|
29
|
-
}
|
|
36
|
+
},
|
|
37
|
+
"main": "dist/index.js"
|
|
30
38
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
import * as os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { toFasta } from './util.ts'
|
|
7
|
+
|
|
8
|
+
import type { InterProScanResponse, InterProScanResults } from 'msa-parsers'
|
|
9
|
+
|
|
10
|
+
const INTERPROSCAN_IMAGE = 'interpro/interproscan:latest'
|
|
11
|
+
|
|
12
|
+
export async function runDockerInterProScan(
|
|
13
|
+
sequences: { id: string; seq: string }[],
|
|
14
|
+
programs: string[],
|
|
15
|
+
): Promise<InterProScanResults[]> {
|
|
16
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'interproscan-'))
|
|
17
|
+
const inputFile = path.join(tmpDir, 'input.fasta')
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
fs.writeFileSync(inputFile, toFasta(sequences), 'utf8')
|
|
21
|
+
|
|
22
|
+
console.log(
|
|
23
|
+
` Running InterProScan via Docker on ${sequences.length} sequences...`,
|
|
24
|
+
)
|
|
25
|
+
console.log(` Image: ${INTERPROSCAN_IMAGE}`)
|
|
26
|
+
|
|
27
|
+
await new Promise<void>((resolve, reject) => {
|
|
28
|
+
const args = [
|
|
29
|
+
'run',
|
|
30
|
+
'--rm',
|
|
31
|
+
'-v',
|
|
32
|
+
`${tmpDir}:/data`,
|
|
33
|
+
INTERPROSCAN_IMAGE,
|
|
34
|
+
'-i',
|
|
35
|
+
'/data/input.fasta',
|
|
36
|
+
'-o',
|
|
37
|
+
'/data/output.json',
|
|
38
|
+
'-f',
|
|
39
|
+
'JSON',
|
|
40
|
+
'-appl',
|
|
41
|
+
programs.join(','),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
console.log(` docker ${args.join(' ')}`)
|
|
45
|
+
|
|
46
|
+
const proc = spawn('docker', args, {
|
|
47
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
51
|
+
const line = data.toString().trim()
|
|
52
|
+
if (line) {
|
|
53
|
+
console.log(` ${line}`)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
let stderr = ''
|
|
58
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
59
|
+
stderr += data.toString()
|
|
60
|
+
const line = data.toString().trim()
|
|
61
|
+
if (line) {
|
|
62
|
+
console.log(` ${line}`)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
proc.on('close', code => {
|
|
67
|
+
if (code === 0) {
|
|
68
|
+
resolve()
|
|
69
|
+
} else {
|
|
70
|
+
reject(
|
|
71
|
+
new Error(
|
|
72
|
+
`Docker InterProScan failed with code ${code}: ${stderr}`,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
proc.on('error', err => {
|
|
79
|
+
reject(
|
|
80
|
+
new Error(
|
|
81
|
+
`Failed to run Docker: ${err.message}. Is Docker installed and running?`,
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const outputFile = path.join(tmpDir, 'output.json')
|
|
88
|
+
if (!fs.existsSync(outputFile)) {
|
|
89
|
+
throw new Error('InterProScan did not produce output file')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const outputContent = fs.readFileSync(outputFile, 'utf8')
|
|
93
|
+
const response: InterProScanResponse = JSON.parse(outputContent)
|
|
94
|
+
|
|
95
|
+
return response.results
|
|
96
|
+
} finally {
|
|
97
|
+
try {
|
|
98
|
+
fs.rmSync(tmpDir, { recursive: true })
|
|
99
|
+
} catch {
|
|
100
|
+
// ignore cleanup errors
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/ebi-api.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import type { InterProScanResults } from 'msa-parsers'
|
|
1
|
+
import type { InterProScanResponse, InterProScanResults } from 'msa-parsers'
|
|
2
2
|
|
|
3
3
|
const BASE_URL = 'https://www.ebi.ac.uk/Tools/services/rest/iprscan5'
|
|
4
4
|
|
|
5
|
-
interface InterProScanResponse {
|
|
6
|
-
results: InterProScanResults[]
|
|
7
|
-
}
|
|
8
|
-
|
|
9
5
|
async function submitJob(
|
|
10
|
-
|
|
6
|
+
sequence: { id: string; seq: string },
|
|
11
7
|
programs: string[],
|
|
12
8
|
email: string,
|
|
13
9
|
): Promise<string> {
|
|
14
|
-
const fastaSeq = sequences.map(s => `>${s.id}\n${s.seq}`).join('\n')
|
|
15
|
-
|
|
16
10
|
const response = await fetch(`${BASE_URL}/run`, {
|
|
17
11
|
method: 'POST',
|
|
18
12
|
headers: {
|
|
@@ -20,13 +14,14 @@ async function submitJob(
|
|
|
20
14
|
},
|
|
21
15
|
body: new URLSearchParams({
|
|
22
16
|
email,
|
|
23
|
-
sequence:
|
|
17
|
+
sequence: `>${sequence.id}\n${sequence.seq}`,
|
|
24
18
|
appl: programs.join(','),
|
|
25
19
|
}),
|
|
26
20
|
})
|
|
27
21
|
|
|
28
22
|
if (!response.ok) {
|
|
29
|
-
|
|
23
|
+
const text = await response.text()
|
|
24
|
+
throw new Error(`Failed to submit job: ${response.statusText} - ${text}`)
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
return response.text()
|
|
@@ -49,9 +44,8 @@ async function getResults(jobId: string): Promise<InterProScanResponse> {
|
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
async function waitForJob(jobId: string): Promise<void> {
|
|
52
|
-
console.log(` Waiting for job ${jobId}...`)
|
|
53
47
|
let attempts = 0
|
|
54
|
-
const maxAttempts = 300
|
|
48
|
+
const maxAttempts = 300
|
|
55
49
|
|
|
56
50
|
while (attempts < maxAttempts) {
|
|
57
51
|
const status = await checkStatus(jobId)
|
|
@@ -78,25 +72,16 @@ export async function runEbiInterProScan(
|
|
|
78
72
|
sequences: { id: string; seq: string }[],
|
|
79
73
|
programs: string[],
|
|
80
74
|
email: string,
|
|
81
|
-
|
|
75
|
+
_batchSize: number,
|
|
82
76
|
): Promise<InterProScanResults[]> {
|
|
83
77
|
const allResults: InterProScanResults[] = []
|
|
84
|
-
const batches: { id: string; seq: string }[][] = []
|
|
85
|
-
|
|
86
|
-
for (let i = 0; i < sequences.length; i += batchSize) {
|
|
87
|
-
batches.push(sequences.slice(i, i + batchSize))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.log(` Submitting ${batches.length} batch(es)...`)
|
|
91
78
|
|
|
92
|
-
for (let i = 0; i <
|
|
93
|
-
const
|
|
94
|
-
console.log(
|
|
95
|
-
` Processing batch ${i + 1}/${batches.length} (${batch.length} sequences)...`,
|
|
96
|
-
)
|
|
79
|
+
for (let i = 0; i < sequences.length; i++) {
|
|
80
|
+
const seq = sequences[i]!
|
|
81
|
+
console.log(` [${i + 1}/${sequences.length}] Submitting ${seq.id}...`)
|
|
97
82
|
|
|
98
|
-
const jobId = await submitJob(
|
|
99
|
-
console.log(` Job
|
|
83
|
+
const jobId = await submitJob(seq, programs, email)
|
|
84
|
+
console.log(` Job: ${jobId}`)
|
|
100
85
|
|
|
101
86
|
await waitForJob(jobId)
|
|
102
87
|
|
|
@@ -105,7 +90,7 @@ export async function runEbiInterProScan(
|
|
|
105
90
|
allResults.push(r)
|
|
106
91
|
}
|
|
107
92
|
|
|
108
|
-
console.log(`
|
|
93
|
+
console.log(` [${i + 1}/${sequences.length}] Done`)
|
|
109
94
|
}
|
|
110
95
|
|
|
111
96
|
return allResults
|
package/src/index.ts
CHANGED
|
@@ -16,13 +16,25 @@ const { values, positionals } = parseArgs({
|
|
|
16
16
|
type: 'boolean',
|
|
17
17
|
default: false,
|
|
18
18
|
},
|
|
19
|
+
docker: {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
23
|
+
singularity: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
'singularity-image': {
|
|
28
|
+
type: 'string',
|
|
29
|
+
default: 'docker://interpro/interproscan:latest',
|
|
30
|
+
},
|
|
19
31
|
'interproscan-path': {
|
|
20
32
|
type: 'string',
|
|
21
33
|
default: 'interproscan.sh',
|
|
22
34
|
},
|
|
23
35
|
programs: {
|
|
24
36
|
type: 'string',
|
|
25
|
-
default: '
|
|
37
|
+
default: 'PfamA,CDD',
|
|
26
38
|
},
|
|
27
39
|
email: {
|
|
28
40
|
type: 'string',
|
|
@@ -53,21 +65,30 @@ COMMANDS:
|
|
|
53
65
|
OPTIONS:
|
|
54
66
|
-o, --output <file> Output GFF file (default: domains.gff)
|
|
55
67
|
--local Use local InterProScan installation
|
|
68
|
+
--docker Use Docker (interpro/interproscan image)
|
|
69
|
+
--singularity Use Singularity/Apptainer container
|
|
70
|
+
--singularity-image <image> Singularity image (default: docker://interpro/interproscan:latest)
|
|
56
71
|
--interproscan-path <path> Path to interproscan.sh (default: interproscan.sh)
|
|
57
|
-
--programs <list> Comma-separated list of programs (default:
|
|
72
|
+
--programs <list> Comma-separated list of programs (default: PfamA,CDD)
|
|
58
73
|
--email <email> Email for EBI API (default: user@example.com)
|
|
59
74
|
--batch-size <n> Number of sequences per API batch (default: 30)
|
|
60
75
|
-h, --help Show this help message
|
|
61
76
|
|
|
62
77
|
EXAMPLES:
|
|
63
|
-
# Run InterProScan using EBI API
|
|
78
|
+
# Run InterProScan using EBI API (one sequence at a time)
|
|
64
79
|
react-msaview-cli interproscan alignment.fasta -o domains.gff
|
|
65
80
|
|
|
66
|
-
# Run with
|
|
81
|
+
# Run with Docker (processes all sequences locally, much faster)
|
|
82
|
+
react-msaview-cli interproscan alignment.fasta -o domains.gff --docker
|
|
83
|
+
|
|
84
|
+
# Run with local InterProScan installation
|
|
67
85
|
react-msaview-cli interproscan alignment.fasta -o domains.gff --local
|
|
68
86
|
|
|
69
|
-
#
|
|
70
|
-
react-msaview-cli interproscan alignment.
|
|
87
|
+
# Run with Singularity using a local .sif file
|
|
88
|
+
react-msaview-cli interproscan alignment.fasta -o domains.gff --singularity --singularity-image /path/to/interproscan.sif
|
|
89
|
+
|
|
90
|
+
# Run with Singularity pulling from Docker Hub (requires network)
|
|
91
|
+
react-msaview-cli interproscan alignment.fasta -o domains.gff --singularity
|
|
71
92
|
`)
|
|
72
93
|
}
|
|
73
94
|
|
|
@@ -90,6 +111,9 @@ async function main() {
|
|
|
90
111
|
inputFile,
|
|
91
112
|
outputFile: values.output,
|
|
92
113
|
useLocal: values.local,
|
|
114
|
+
useDocker: values.docker,
|
|
115
|
+
useSingularity: values.singularity,
|
|
116
|
+
singularityImage: values['singularity-image'],
|
|
93
117
|
interproscanPath: values['interproscan-path'],
|
|
94
118
|
programs: values.programs.split(','),
|
|
95
119
|
email: values.email,
|
package/src/interproscan-msa.ts
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
parseMSA,
|
|
7
7
|
} from 'msa-parsers'
|
|
8
8
|
|
|
9
|
+
import { runDockerInterProScan } from './docker-runner'
|
|
9
10
|
import { runEbiInterProScan } from './ebi-api'
|
|
10
11
|
import { runLocalInterProScan } from './local-runner'
|
|
12
|
+
import { runSingularityInterProScan } from './singularity-runner'
|
|
11
13
|
|
|
12
14
|
import type { InterProScanResults } from 'msa-parsers'
|
|
13
15
|
|
|
@@ -15,6 +17,9 @@ export interface InterProScanOptions {
|
|
|
15
17
|
inputFile: string
|
|
16
18
|
outputFile: string
|
|
17
19
|
useLocal: boolean
|
|
20
|
+
useDocker: boolean
|
|
21
|
+
useSingularity: boolean
|
|
22
|
+
singularityImage: string
|
|
18
23
|
interproscanPath: string
|
|
19
24
|
programs: string[]
|
|
20
25
|
email: string
|
|
@@ -26,6 +31,9 @@ export async function runInterProScan(options: InterProScanOptions) {
|
|
|
26
31
|
inputFile,
|
|
27
32
|
outputFile,
|
|
28
33
|
useLocal,
|
|
34
|
+
useDocker,
|
|
35
|
+
useSingularity,
|
|
36
|
+
singularityImage,
|
|
29
37
|
interproscanPath,
|
|
30
38
|
programs,
|
|
31
39
|
email,
|
|
@@ -52,7 +60,17 @@ export async function runInterProScan(options: InterProScanOptions) {
|
|
|
52
60
|
|
|
53
61
|
let allResults: InterProScanResults[]
|
|
54
62
|
|
|
55
|
-
if (
|
|
63
|
+
if (useSingularity) {
|
|
64
|
+
console.log('Running InterProScan via Singularity...')
|
|
65
|
+
allResults = await runSingularityInterProScan(
|
|
66
|
+
sequences,
|
|
67
|
+
programs,
|
|
68
|
+
singularityImage,
|
|
69
|
+
)
|
|
70
|
+
} else if (useDocker) {
|
|
71
|
+
console.log('Running InterProScan via Docker...')
|
|
72
|
+
allResults = await runDockerInterProScan(sequences, programs)
|
|
73
|
+
} else if (useLocal) {
|
|
56
74
|
console.log(`Running local InterProScan at ${interproscanPath}...`)
|
|
57
75
|
allResults = await runLocalInterProScan(
|
|
58
76
|
sequences,
|
|
@@ -60,11 +78,11 @@ export async function runInterProScan(options: InterProScanOptions) {
|
|
|
60
78
|
programs,
|
|
61
79
|
)
|
|
62
80
|
} else {
|
|
63
|
-
console.log(
|
|
81
|
+
console.log('Running InterProScan via EBI API...')
|
|
64
82
|
allResults = await runEbiInterProScan(sequences, programs, email, batchSize)
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
console.log(
|
|
85
|
+
console.log('Converting results to GFF...')
|
|
68
86
|
const gff = interProResponseToGFF(allResults)
|
|
69
87
|
|
|
70
88
|
console.log(`Writing output to ${outputFile}...`)
|
package/src/local-runner.ts
CHANGED
|
@@ -3,11 +3,9 @@ import * as fs from 'node:fs'
|
|
|
3
3
|
import * as os from 'node:os'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { toFasta } from './util.ts'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
results: InterProScanResults[]
|
|
10
|
-
}
|
|
8
|
+
import type { InterProScanResponse, InterProScanResults } from 'msa-parsers'
|
|
11
9
|
|
|
12
10
|
export async function runLocalInterProScan(
|
|
13
11
|
sequences: { id: string; seq: string }[],
|
|
@@ -19,9 +17,7 @@ export async function runLocalInterProScan(
|
|
|
19
17
|
const outputFile = path.join(tmpDir, 'output.json')
|
|
20
18
|
|
|
21
19
|
try {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fs.writeFileSync(inputFile, fastaContent, 'utf8')
|
|
20
|
+
fs.writeFileSync(inputFile, toFasta(sequences), 'utf8')
|
|
25
21
|
|
|
26
22
|
console.log(` Running InterProScan on ${sequences.length} sequences...`)
|
|
27
23
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import * as fs from 'node:fs'
|
|
3
|
+
import * as os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { toFasta } from './util.ts'
|
|
7
|
+
|
|
8
|
+
import type { InterProScanResponse, InterProScanResults } from 'msa-parsers'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SINGULARITY_IMAGE = 'docker://interpro/interproscan:latest'
|
|
11
|
+
|
|
12
|
+
export async function runSingularityInterProScan(
|
|
13
|
+
sequences: { id: string; seq: string }[],
|
|
14
|
+
programs: string[],
|
|
15
|
+
imagePath = DEFAULT_SINGULARITY_IMAGE,
|
|
16
|
+
): Promise<InterProScanResults[]> {
|
|
17
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'interproscan-'))
|
|
18
|
+
const inputFile = path.join(tmpDir, 'input.fasta')
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
fs.writeFileSync(inputFile, toFasta(sequences), 'utf8')
|
|
22
|
+
|
|
23
|
+
console.log(
|
|
24
|
+
` Running InterProScan via Singularity on ${sequences.length} sequences...`,
|
|
25
|
+
)
|
|
26
|
+
console.log(` Image: ${imagePath}`)
|
|
27
|
+
|
|
28
|
+
await new Promise<void>((resolve, reject) => {
|
|
29
|
+
const args = [
|
|
30
|
+
'exec',
|
|
31
|
+
'--bind',
|
|
32
|
+
`${tmpDir}:/data`,
|
|
33
|
+
imagePath,
|
|
34
|
+
'/opt/interproscan/interproscan.sh',
|
|
35
|
+
'-i',
|
|
36
|
+
'/data/input.fasta',
|
|
37
|
+
'-o',
|
|
38
|
+
'/data/output.json',
|
|
39
|
+
'-f',
|
|
40
|
+
'JSON',
|
|
41
|
+
'-appl',
|
|
42
|
+
programs.join(','),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
console.log(` singularity ${args.join(' ')}`)
|
|
46
|
+
|
|
47
|
+
const proc = spawn('singularity', args, {
|
|
48
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
proc.stdout.on('data', (data: Buffer) => {
|
|
52
|
+
const line = data.toString().trim()
|
|
53
|
+
if (line) {
|
|
54
|
+
console.log(` ${line}`)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
let stderr = ''
|
|
59
|
+
proc.stderr.on('data', (data: Buffer) => {
|
|
60
|
+
stderr += data.toString()
|
|
61
|
+
const line = data.toString().trim()
|
|
62
|
+
if (line) {
|
|
63
|
+
console.log(` ${line}`)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
proc.on('close', code => {
|
|
68
|
+
if (code === 0) {
|
|
69
|
+
resolve()
|
|
70
|
+
} else {
|
|
71
|
+
reject(
|
|
72
|
+
new Error(
|
|
73
|
+
`Singularity InterProScan failed with code ${code}: ${stderr}`,
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
proc.on('error', err => {
|
|
80
|
+
reject(
|
|
81
|
+
new Error(
|
|
82
|
+
`Failed to run Singularity: ${err.message}. Is Singularity/Apptainer installed?`,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const outputFile = path.join(tmpDir, 'output.json')
|
|
89
|
+
if (!fs.existsSync(outputFile)) {
|
|
90
|
+
throw new Error('InterProScan did not produce output file')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const outputContent = fs.readFileSync(outputFile, 'utf8')
|
|
94
|
+
const response: InterProScanResponse = JSON.parse(outputContent)
|
|
95
|
+
|
|
96
|
+
return response.results
|
|
97
|
+
} finally {
|
|
98
|
+
try {
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true })
|
|
100
|
+
} catch {
|
|
101
|
+
// ignore cleanup errors
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/util.ts
ADDED