epress-cli 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 +97 -0
- package/cli.js +146 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# epress - Fast File Sharing CLI
|
|
2
|
+
|
|
3
|
+
A super fast CLI tool to upload and download compressed files across machines.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Setup Backend (Next.js)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd backend
|
|
11
|
+
npm install
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The backend will run on `http://localhost:3000`
|
|
16
|
+
|
|
17
|
+
### 2. Setup Vercel Blob Storage
|
|
18
|
+
|
|
19
|
+
1. Go to [Vercel Dashboard](https://vercel.com) → Your Team → Settings → Storage
|
|
20
|
+
2. Create a new Blob Store (or use existing)
|
|
21
|
+
3. Copy the `BLOB_READ_WRITE_TOKEN`
|
|
22
|
+
4. Add it as an environment variable in Vercel:
|
|
23
|
+
- Go to your project → Settings → Environment Variables
|
|
24
|
+
- Add: `BLOB_READ_WRITE_TOKEN` = your token
|
|
25
|
+
|
|
26
|
+
### 3. Deploy Backend (Vercel - Recommended)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd backend
|
|
30
|
+
npm install
|
|
31
|
+
vercel
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
After deployment, set the API URL:
|
|
35
|
+
```bash
|
|
36
|
+
export EPRESS_API_URL=https://your-app.vercel.app/api
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 4. Use CLI Tool
|
|
40
|
+
|
|
41
|
+
#### Upload a file or folder:
|
|
42
|
+
```bash
|
|
43
|
+
npx epress post ./my-folder
|
|
44
|
+
npx epress post ./file.txt
|
|
45
|
+
npx epress post ./my-folder --name "my-project"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### Download by ID:
|
|
49
|
+
```bash
|
|
50
|
+
npx epress pull <id>
|
|
51
|
+
npx epress pull <id> --output ./downloads
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
### Local Development
|
|
57
|
+
|
|
58
|
+
1. Start backend:
|
|
59
|
+
```bash
|
|
60
|
+
cd backend
|
|
61
|
+
npm run dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Test CLI locally:
|
|
65
|
+
```bash
|
|
66
|
+
node cli.js post ./test-folder
|
|
67
|
+
node cli.js pull <id>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Publishing CLI to npm
|
|
71
|
+
|
|
72
|
+
1. Update package.json with your npm username
|
|
73
|
+
2. Login to npm: `npm login`
|
|
74
|
+
3. Publish: `npm publish`
|
|
75
|
+
|
|
76
|
+
## Environment Variables
|
|
77
|
+
|
|
78
|
+
### CLI
|
|
79
|
+
- `EPRESS_API_URL` - Backend API URL (default: http://localhost:3000/api)
|
|
80
|
+
|
|
81
|
+
### Backend (Vercel)
|
|
82
|
+
- `BLOB_READ_WRITE_TOKEN` - Vercel Blob storage token (required for production)
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- ✅ Upload files/folders as compressed zip
|
|
87
|
+
- ✅ Download and extract files by ID
|
|
88
|
+
- ✅ Works cross-platform (Windows, Mac, Linux)
|
|
89
|
+
- ✅ Fast deployment on Vercel
|
|
90
|
+
- ✅ Persistent storage with Vercel Blob
|
|
91
|
+
- ✅ No database needed
|
|
92
|
+
|
|
93
|
+
## Notes
|
|
94
|
+
|
|
95
|
+
- Files are stored in Vercel Blob storage (persistent and fast)
|
|
96
|
+
- Add authentication if needed
|
|
97
|
+
- Set up file expiration/cleanup for old uploads
|
package/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const FormData = require('form-data');
|
|
7
|
+
const axios = require('axios');
|
|
8
|
+
const archiver = require('archiver');
|
|
9
|
+
const extract = require('extract-zip');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
// Get API URL from environment or use default
|
|
13
|
+
const API_URL = process.env.EPRESS_API_URL || 'https://espress-two.vercel.app/api';
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('epress')
|
|
17
|
+
.description('CLI tool to upload and download compressed files')
|
|
18
|
+
.version('1.0.0');
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command('post <path>')
|
|
22
|
+
.description('Upload a file or folder as compressed zip')
|
|
23
|
+
.option('-n, --name <name>', 'Custom ID for the upload (e.g., hd1234)')
|
|
24
|
+
.action(async (filePath, options) => {
|
|
25
|
+
try {
|
|
26
|
+
console.log(chalk.blue('📦 Compressing...'));
|
|
27
|
+
|
|
28
|
+
const fullPath = path.resolve(filePath);
|
|
29
|
+
if (!fs.existsSync(fullPath)) {
|
|
30
|
+
console.error(chalk.red('❌ File or folder not found:', filePath));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const stats = fs.statSync(fullPath);
|
|
35
|
+
const isDirectory = stats.isDirectory();
|
|
36
|
+
|
|
37
|
+
// Create zip file
|
|
38
|
+
const zipPath = path.join(__dirname, 'temp-upload.zip');
|
|
39
|
+
const output = fs.createWriteStream(zipPath);
|
|
40
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
output.on('close', async () => {
|
|
44
|
+
try {
|
|
45
|
+
console.log(chalk.blue('📤 Uploading...'));
|
|
46
|
+
|
|
47
|
+
const form = new FormData();
|
|
48
|
+
form.append('file', fs.createReadStream(zipPath));
|
|
49
|
+
if (options.name) {
|
|
50
|
+
form.append('name', options.name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const response = await axios.post(`${API_URL}/upload`, form, {
|
|
54
|
+
headers: form.getHeaders(),
|
|
55
|
+
maxContentLength: Infinity,
|
|
56
|
+
maxBodyLength: Infinity,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Clean up temp file
|
|
60
|
+
fs.unlinkSync(zipPath);
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green('✅ Upload successful!'));
|
|
63
|
+
console.log(chalk.cyan('📋 ID:'), response.data.id);
|
|
64
|
+
console.log(chalk.cyan('🔗 URL:'), `${API_URL.replace('/api', '')}/api/download/${response.data.id}`);
|
|
65
|
+
console.log(chalk.yellow('\n💡 Use this command to download:'));
|
|
66
|
+
console.log(chalk.white(` npx epress pull ${response.data.id}`));
|
|
67
|
+
|
|
68
|
+
resolve();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
fs.unlinkSync(zipPath);
|
|
71
|
+
console.error(chalk.red('❌ Upload failed:'), error.response?.data?.error || error.message);
|
|
72
|
+
reject(error);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
archive.on('error', (err) => {
|
|
77
|
+
console.error(chalk.red('❌ Compression failed:'), err);
|
|
78
|
+
reject(err);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
archive.pipe(output);
|
|
82
|
+
|
|
83
|
+
if (isDirectory) {
|
|
84
|
+
archive.directory(fullPath, false);
|
|
85
|
+
} else {
|
|
86
|
+
archive.file(fullPath, { name: path.basename(fullPath) });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
archive.finalize();
|
|
90
|
+
});
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(chalk.red('❌ Error:'), error.message);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
program
|
|
98
|
+
.command('pull <id>')
|
|
99
|
+
.description('Download and extract a file by ID')
|
|
100
|
+
.option('-o, --output <dir>', 'Output directory (default: current directory)')
|
|
101
|
+
.action(async (id, options) => {
|
|
102
|
+
try {
|
|
103
|
+
const outputDir = options.output ? path.resolve(options.output) : process.cwd();
|
|
104
|
+
|
|
105
|
+
if (!fs.existsSync(outputDir)) {
|
|
106
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(chalk.blue('📥 Downloading...'));
|
|
110
|
+
|
|
111
|
+
const response = await axios.get(`${API_URL}/download/${id}`, {
|
|
112
|
+
responseType: 'stream',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const zipPath = path.join(__dirname, `temp-${id}.zip`);
|
|
116
|
+
const writer = fs.createWriteStream(zipPath);
|
|
117
|
+
|
|
118
|
+
response.data.pipe(writer);
|
|
119
|
+
|
|
120
|
+
await new Promise((resolve, reject) => {
|
|
121
|
+
writer.on('finish', async () => {
|
|
122
|
+
try {
|
|
123
|
+
console.log(chalk.blue('📦 Extracting...'));
|
|
124
|
+
await extract(zipPath, { dir: outputDir });
|
|
125
|
+
fs.unlinkSync(zipPath);
|
|
126
|
+
console.log(chalk.green(`✅ Extracted to: ${outputDir}`));
|
|
127
|
+
resolve();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
fs.unlinkSync(zipPath);
|
|
130
|
+
console.error(chalk.red('❌ Extraction failed:'), error.message);
|
|
131
|
+
reject(error);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
writer.on('error', reject);
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error.response?.status === 404) {
|
|
138
|
+
console.error(chalk.red('❌ File not found. Invalid ID.'));
|
|
139
|
+
} else {
|
|
140
|
+
console.error(chalk.red('❌ Download failed:'), error.response?.data?.error || error.message);
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "epress-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to upload and download compressed files across machines",
|
|
5
|
+
"main": "cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"epress": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "cd backend && npm run dev",
|
|
15
|
+
"build": "cd backend && npm run build",
|
|
16
|
+
"start": "cd backend && npm start"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"cli",
|
|
20
|
+
"file-sharing",
|
|
21
|
+
"upload",
|
|
22
|
+
"download",
|
|
23
|
+
"file-transfer",
|
|
24
|
+
"zip",
|
|
25
|
+
"compress"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/yourusername/epress.git"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=14.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"commander": "^11.1.0",
|
|
38
|
+
"form-data": "^4.0.0",
|
|
39
|
+
"axios": "^1.6.2",
|
|
40
|
+
"archiver": "^6.0.1",
|
|
41
|
+
"extract-zip": "^2.0.1",
|
|
42
|
+
"chalk": "^4.1.2"
|
|
43
|
+
}
|
|
44
|
+
}
|