ez-reads 1.0.0
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 +100 -0
- package/bin/paper-site.mjs +6 -0
- package/bin/rebuild-all.mjs +25 -0
- package/package.json +47 -0
- package/src/cli.mjs +243 -0
- package/src/distiller.mjs +356 -0
- package/src/fetcher.mjs +327 -0
- package/src/generator.mjs +656 -0
- package/template/index.html +15 -0
- package/template/package-lock.json +2659 -0
- package/template/package.json +22 -0
- package/template/postcss.config.js +6 -0
- package/template/src/App.jsx +62 -0
- package/template/src/components/Abstract.jsx +18 -0
- package/template/src/components/Analogy.jsx +20 -0
- package/template/src/components/ChatAssistant.jsx +155 -0
- package/template/src/components/Figures.jsx +63 -0
- package/template/src/components/Footer.jsx +22 -0
- package/template/src/components/Glossary.jsx +36 -0
- package/template/src/components/Hero.jsx +56 -0
- package/template/src/components/KeyContributions.jsx +35 -0
- package/template/src/components/Limitations.jsx +22 -0
- package/template/src/components/Methodology.jsx +42 -0
- package/template/src/components/Results.jsx +34 -0
- package/template/src/components/Significance.jsx +20 -0
- package/template/src/components/Stats.jsx +34 -0
- package/template/src/data/paper.json +53 -0
- package/template/src/index.css +327 -0
- package/template/src/main.jsx +10 -0
- package/template/tailwind.config.js +12 -0
- package/template/vite.config.js +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ez-reads
|
|
2
|
+
|
|
3
|
+
Turn any research paper into a beautiful, interactive static website.
|
|
4
|
+
|
|
5
|
+
Paste an ArXiv URL or DOI, and ez-reads will fetch the paper, distill it into structured sections using an LLM, and generate a polished React site — complete with an AI chat assistant that can answer questions about the paper.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Automatic distillation** — Extracts key contributions, methodology, results, stats, significance, limitations, glossary, and more
|
|
10
|
+
- **Beautiful sites** — React + Tailwind sites with scroll animations, color themes, and responsive design
|
|
11
|
+
- **AI chat assistant** — Each generated site includes a chat widget powered by Groq so visitors can ask questions about the paper
|
|
12
|
+
- **Paper library** — An auto-generated index page lists all your papers with search
|
|
13
|
+
- **ArXiv + DOI support** — Works with ArXiv URLs and DOIs from any publisher
|
|
14
|
+
- **Figure extraction** — Automatically downloads and displays paper figures
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- **Node.js 18+** (uses native `fetch()`)
|
|
19
|
+
- **Groq API key** — free at [console.groq.com/keys](https://console.groq.com/keys)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g ez-reads
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or run it directly without installing:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx ez-reads
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
ez-reads
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On first run, you'll be prompted for your Groq API key. The key powers both:
|
|
40
|
+
1. **Paper distillation** — extracting structured data from the paper via LLM
|
|
41
|
+
2. **Chat assistant** — the interactive Q&A widget on each generated site
|
|
42
|
+
|
|
43
|
+
**Model used:** `qwen/qwen3-32b` (available on Groq's free tier)
|
|
44
|
+
|
|
45
|
+
### Skip the prompt
|
|
46
|
+
|
|
47
|
+
Set your key in the environment to skip the interactive prompt:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
export GROQ_API_KEY=gsk_your_key_here
|
|
51
|
+
ez-reads
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or create a `.env` file in the directory where you save the key:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
GROQ_API_KEY=gsk_your_key_here
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How it works
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
📄 paper → 🔬 distill → ✨ generate → 🌐 site
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
1. **Fetch** — Scrapes the paper content from ArXiv or resolves a DOI
|
|
67
|
+
2. **Distill** — Sends the paper through 5-6 LLM calls to extract structured JSON (title, abstract, methodology, results, stats, glossary, etc.)
|
|
68
|
+
3. **Generate** — Builds a React + Tailwind static site and serves it locally
|
|
69
|
+
|
|
70
|
+
Output goes to `./ez-reads-output/` in your current directory.
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
| Command | Description |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `ez-reads` or `paper-site` | Start the interactive CLI |
|
|
77
|
+
| `npm run rebuild` | Rebuild all existing sites (after template changes) |
|
|
78
|
+
|
|
79
|
+
## Generated site structure
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
ez-reads-output/
|
|
83
|
+
├── index.html # Library page (lists all papers)
|
|
84
|
+
└── <paper-slug>/
|
|
85
|
+
├── index.html # Paper site
|
|
86
|
+
├── assets/ # JS/CSS bundles
|
|
87
|
+
├── figures/ # Downloaded figures
|
|
88
|
+
├── meta.json # Metadata for library index
|
|
89
|
+
└── data.json # Full distilled data (for rebuilds)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Chat assistant
|
|
93
|
+
|
|
94
|
+
Every generated site includes a floating chat button in the bottom-right corner. Visitors can ask questions about the paper and get answers powered by the same Groq API key used during generation.
|
|
95
|
+
|
|
96
|
+
**Note:** Your Groq API key is embedded in the generated site's JavaScript bundle to power the chat assistant. This is fine for local use or private hosting. If you plan to host generated sites publicly, be aware that the key is visible in the page source.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { rebuildAll } from '../src/generator.mjs';
|
|
8
|
+
|
|
9
|
+
const OUTPUT_DIR = path.resolve(process.cwd(), 'ez-reads-output');
|
|
10
|
+
|
|
11
|
+
console.log(chalk.blueBright('\n Rebuilding all paper sites with updated template...\n'));
|
|
12
|
+
|
|
13
|
+
const spinner = ora('Rebuilding...').start();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const results = await rebuildAll(OUTPUT_DIR);
|
|
17
|
+
spinner.succeed(`Rebuilt ${results.length} site${results.length === 1 ? '' : 's'}`);
|
|
18
|
+
for (const r of results) {
|
|
19
|
+
console.log(` ${chalk.green('✓')} ${r.slug}`);
|
|
20
|
+
}
|
|
21
|
+
console.log('');
|
|
22
|
+
} catch (err) {
|
|
23
|
+
spinner.fail(`Rebuild failed: ${err.message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ez-reads",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Turn any research paper into a beautiful static website",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ez-reads": "bin/paper-site.mjs",
|
|
8
|
+
"paper-site": "bin/paper-site.mjs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/paper-site.mjs",
|
|
12
|
+
"rebuild": "node bin/rebuild-all.mjs"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"src/",
|
|
17
|
+
"template/",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"arxiv",
|
|
22
|
+
"research",
|
|
23
|
+
"paper",
|
|
24
|
+
"static-site",
|
|
25
|
+
"groq",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/hsidiyer/ez-reads.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/hsidiyer/ez-reads#readme",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^5.3.0",
|
|
40
|
+
"cheerio": "^1.0.0-rc.12",
|
|
41
|
+
"dotenv": "^17.2.4",
|
|
42
|
+
"fs-extra": "^11.2.0",
|
|
43
|
+
"groq-sdk": "^0.37.0",
|
|
44
|
+
"inquirer": "^9.2.12",
|
|
45
|
+
"ora": "^8.0.1"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { fetchPaper } from './fetcher.mjs';
|
|
9
|
+
import { distillPaper } from './distiller.mjs';
|
|
10
|
+
import { generateSite } from './generator.mjs';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const OUTPUT_DIR = path.resolve(process.cwd(), 'ez-reads-output');
|
|
14
|
+
|
|
15
|
+
const gd = chalk.blueBright;
|
|
16
|
+
const dm = chalk.dim;
|
|
17
|
+
const wh = chalk.white;
|
|
18
|
+
const cn = chalk.cyan;
|
|
19
|
+
|
|
20
|
+
const BANNER = `
|
|
21
|
+
${dm(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
|
|
22
|
+
|
|
23
|
+
${gd.bold(' ███████ █████ ███████ ██ ██')}
|
|
24
|
+
${gd.bold(' ██ ██ ██ ██ ██ ██ ')}
|
|
25
|
+
${gd.bold(' █████ ███████ ███████ ████ ')}
|
|
26
|
+
${gd.bold(' ██ ██ ██ ██ ██ ')}
|
|
27
|
+
${gd.bold(' ███████ ██ ██ ███████ ██ ')}
|
|
28
|
+
|
|
29
|
+
${gd.bold(' ██████ ███████ █████ ██████ ███████')}
|
|
30
|
+
${gd.bold(' ██ ██ ██ ██ ██ ██ ██ ██ ')}
|
|
31
|
+
${gd.bold(' ██████ █████ ███████ ██ ██ ███████')}
|
|
32
|
+
${gd.bold(' ██ ██ ██ ██ ██ ██ ██ ██')}
|
|
33
|
+
${gd.bold(' ██ ██ ███████ ██ ██ ██████ ███████')}
|
|
34
|
+
|
|
35
|
+
${wh.bold(' Make beautiful sites from research papers.')}
|
|
36
|
+
|
|
37
|
+
${dm(' 📄')} ${cn('paper')} ${dm('→')} ${dm('🔬')} ${cn('distill')} ${dm('→')} ${dm('✨')} ${cn('generate')} ${dm('→')} ${dm('🌐')} ${cn('site')}
|
|
38
|
+
|
|
39
|
+
${dm(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export async function run() {
|
|
43
|
+
console.log(BANNER);
|
|
44
|
+
|
|
45
|
+
// Resolve API key — use env/dotenv if available, otherwise prompt
|
|
46
|
+
if (!process.env.GROQ_API_KEY) {
|
|
47
|
+
console.log(
|
|
48
|
+
chalk.yellow(' ⚡ A Groq API key is required to distill papers and power the chat assistant.\n') +
|
|
49
|
+
chalk.dim(' Get one free at: ') + chalk.cyan('https://console.groq.com/keys') + '\n' +
|
|
50
|
+
chalk.dim(' Model: ') + chalk.white('qwen/qwen3-32b') + chalk.dim(' (free tier)\n') +
|
|
51
|
+
chalk.dim(' Tip: set ') + chalk.white('GROQ_API_KEY=gsk_...') + chalk.dim(' in a .env file to skip this prompt.\n')
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const { apiKey } = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'password',
|
|
57
|
+
name: 'apiKey',
|
|
58
|
+
message: 'Enter your Groq API key:',
|
|
59
|
+
mask: '*',
|
|
60
|
+
validate: (val) => {
|
|
61
|
+
if (!val.trim()) return 'API key is required.';
|
|
62
|
+
if (!val.trim().startsWith('gsk_')) return 'Groq API keys start with gsk_. Get one at https://console.groq.com/keys';
|
|
63
|
+
return true;
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
process.env.GROQ_API_KEY = apiKey.trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.dim(' Model: ') + chalk.cyan('qwen/qwen3-32b') + chalk.dim(' via Groq') +
|
|
73
|
+
chalk.dim(' • API key: ') + chalk.green('✓ loaded') +
|
|
74
|
+
chalk.dim(' • Powers: distillation + chat assistant\n')
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Start the server once — it serves the entire output/ directory
|
|
78
|
+
const { port } = await startStaticServer(OUTPUT_DIR);
|
|
79
|
+
const libraryUrl = `http://localhost:${port}/`;
|
|
80
|
+
|
|
81
|
+
console.log(` ${chalk.bold('Library:')} ${chalk.cyan(libraryUrl)}\n`);
|
|
82
|
+
|
|
83
|
+
// Process papers in a loop
|
|
84
|
+
let paperCount = 0;
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
const result = await processPaper(port);
|
|
88
|
+
|
|
89
|
+
if (result) {
|
|
90
|
+
paperCount++;
|
|
91
|
+
const paperUrl = `http://localhost:${port}/${result.slug}/`;
|
|
92
|
+
|
|
93
|
+
console.log(
|
|
94
|
+
`\n ${chalk.green('Site is live!')}\n` +
|
|
95
|
+
`\n ${chalk.bold('This paper:')} ${chalk.cyan(paperUrl)}` +
|
|
96
|
+
`\n ${chalk.bold('Library:')} ${chalk.cyan(libraryUrl)}` +
|
|
97
|
+
`\n ${chalk.dim(`${paperCount} paper${paperCount === 1 ? '' : 's'} generated this session.`)}\n`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Ask if the user wants to add another paper
|
|
102
|
+
const { another } = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'confirm',
|
|
105
|
+
name: 'another',
|
|
106
|
+
message: 'Add another paper?',
|
|
107
|
+
default: true,
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
if (!another) break;
|
|
112
|
+
|
|
113
|
+
console.log(''); // spacing
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
`\n ${chalk.dim('Server still running at')} ${chalk.cyan(libraryUrl)}` +
|
|
118
|
+
`\n ${chalk.dim('Press Ctrl+C to stop.')}\n`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------- process a single paper ----------
|
|
123
|
+
|
|
124
|
+
async function processPaper(port) {
|
|
125
|
+
// Prompt for paper link
|
|
126
|
+
const { input } = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'input',
|
|
129
|
+
name: 'input',
|
|
130
|
+
message: 'Enter an ArXiv URL or DOI:',
|
|
131
|
+
validate: (val) => {
|
|
132
|
+
val = val.trim();
|
|
133
|
+
if (!val) return 'Please enter a URL or DOI.';
|
|
134
|
+
if (/arxiv\.org\/(abs|html|pdf)\//.test(val)) return true;
|
|
135
|
+
if (/^10\.\d{4,}\//.test(val) || /doi\.org\/10\.\d{4,}\//.test(val)) return true;
|
|
136
|
+
return 'Please enter a valid ArXiv URL or DOI (e.g., https://arxiv.org/abs/2401.00001 or 10.1234/example)';
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
// Fetch
|
|
142
|
+
const fetchSpinner = ora('Fetching paper content...').start();
|
|
143
|
+
let paperData;
|
|
144
|
+
try {
|
|
145
|
+
paperData = await fetchPaper(input.trim());
|
|
146
|
+
fetchSpinner.succeed(
|
|
147
|
+
`Fetched: ${chalk.bold(paperData.title || 'paper')} (${paperData.sections.length} sections)`
|
|
148
|
+
);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
fetchSpinner.fail(`Failed to fetch paper: ${err.message}`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Distill
|
|
155
|
+
const distillSpinner = ora('Distilling paper...').start();
|
|
156
|
+
let distilled;
|
|
157
|
+
try {
|
|
158
|
+
distilled = await distillPaper(paperData, (chunkName, current, total) => {
|
|
159
|
+
distillSpinner.text = `Distilling: ${chunkName} (${current}/${total})...`;
|
|
160
|
+
});
|
|
161
|
+
distilled.url = paperData.url;
|
|
162
|
+
distilled.publishedDate = paperData.publishedDate || null;
|
|
163
|
+
distillSpinner.succeed('Paper distilled into structured data (5 chunks)');
|
|
164
|
+
} catch (err) {
|
|
165
|
+
distillSpinner.fail(`Failed to distill paper: ${err.message}`);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Build
|
|
170
|
+
const genSpinner = ora('Building website...').start();
|
|
171
|
+
try {
|
|
172
|
+
const result = await generateSite(distilled, OUTPUT_DIR);
|
|
173
|
+
genSpinner.succeed(`Built: ${chalk.cyan(result.slug)}`);
|
|
174
|
+
return result;
|
|
175
|
+
} catch (err) {
|
|
176
|
+
genSpinner.fail(`Failed to build site: ${err.message}`);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------- static file server ----------
|
|
182
|
+
|
|
183
|
+
const MIME_TYPES = {
|
|
184
|
+
'.html': 'text/html; charset=utf-8',
|
|
185
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
186
|
+
'.css': 'text/css; charset=utf-8',
|
|
187
|
+
'.json': 'application/json; charset=utf-8',
|
|
188
|
+
'.png': 'image/png',
|
|
189
|
+
'.jpg': 'image/jpeg',
|
|
190
|
+
'.jpeg': 'image/jpeg',
|
|
191
|
+
'.gif': 'image/gif',
|
|
192
|
+
'.svg': 'image/svg+xml',
|
|
193
|
+
'.ico': 'image/x-icon',
|
|
194
|
+
'.woff': 'font/woff',
|
|
195
|
+
'.woff2':'font/woff2',
|
|
196
|
+
'.ttf': 'font/ttf',
|
|
197
|
+
'.webp': 'image/webp',
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
function startStaticServer(rootDir, port = 3000) {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
// Ensure the output directory exists before serving
|
|
203
|
+
fs.ensureDirSync(rootDir);
|
|
204
|
+
|
|
205
|
+
const server = http.createServer(async (req, res) => {
|
|
206
|
+
const urlPath = decodeURIComponent(req.url.split('?')[0]);
|
|
207
|
+
let filePath = path.join(rootDir, urlPath);
|
|
208
|
+
|
|
209
|
+
// If it's a directory, serve index.html
|
|
210
|
+
try {
|
|
211
|
+
const stat = await fs.stat(filePath);
|
|
212
|
+
if (stat.isDirectory()) {
|
|
213
|
+
filePath = path.join(filePath, 'index.html');
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// file doesn't exist — will 404 below
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const data = await fs.readFile(filePath);
|
|
221
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
222
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
223
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
224
|
+
res.end(data);
|
|
225
|
+
} catch {
|
|
226
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
227
|
+
res.end('Not found');
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
server.listen(port, () => {
|
|
232
|
+
resolve({ server, port });
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
server.on('error', (err) => {
|
|
236
|
+
if (err.code === 'EADDRINUSE') {
|
|
237
|
+
startStaticServer(rootDir, port + 1).then(resolve, reject);
|
|
238
|
+
} else {
|
|
239
|
+
reject(err);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|