laju-server 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 +45 -0
- package/bin/cli.js +25 -0
- package/index.js +140 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# laju-server
|
|
2
|
+
|
|
3
|
+
Instant static file server menggunakan hyper-express. Cepat dan ringan!
|
|
4
|
+
|
|
5
|
+
## Instalasi
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g laju-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Penggunaan
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Serve folder saat ini
|
|
15
|
+
npx laju-server
|
|
16
|
+
|
|
17
|
+
# Serve folder tertentu
|
|
18
|
+
npx laju-server ./public
|
|
19
|
+
|
|
20
|
+
# Serve dengan port custom
|
|
21
|
+
npx laju-server ./dist -p 8080
|
|
22
|
+
npx laju-server ./dist --port 8080
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Fitur
|
|
26
|
+
|
|
27
|
+
- ⚡ Super cepat dengan hyper-express
|
|
28
|
+
- 📁 Serve folder statis
|
|
29
|
+
- 🔒 Proteksi directory traversal
|
|
30
|
+
- 📄 Auto serve index.html
|
|
31
|
+
- 🎨 Auto detect MIME types
|
|
32
|
+
|
|
33
|
+
## Contoh
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Serve folder build React
|
|
37
|
+
npx laju-server ./build
|
|
38
|
+
|
|
39
|
+
# Serve folder dist Vite
|
|
40
|
+
npx laju-server ./dist -p 4000
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { startServer } = require('../index.js');
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
// Parse arguments
|
|
9
|
+
let folder = '.';
|
|
10
|
+
let port = 3000;
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
if (args[i] === '-p' || args[i] === '--port') {
|
|
14
|
+
port = parseInt(args[i + 1], 10) || 3000;
|
|
15
|
+
i++;
|
|
16
|
+
} else if (!args[i].startsWith('-')) {
|
|
17
|
+
folder = args[i];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Resolve folder path
|
|
22
|
+
const folderPath = path.resolve(process.cwd(), folder);
|
|
23
|
+
|
|
24
|
+
// Start server
|
|
25
|
+
startServer(folderPath, port);
|
package/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const HyperExpress = require('hyper-express');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const mime = require('mime-types');
|
|
5
|
+
|
|
6
|
+
function startServer(folderPath, port = 3000) {
|
|
7
|
+
// Check if folder exists
|
|
8
|
+
if (!fs.existsSync(folderPath)) {
|
|
9
|
+
console.error(`❌ Folder tidak ditemukan: ${folderPath}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const stats = fs.statSync(folderPath);
|
|
14
|
+
if (!stats.isDirectory()) {
|
|
15
|
+
console.error(`❌ Path bukan folder: ${folderPath}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const server = new HyperExpress.Server();
|
|
20
|
+
|
|
21
|
+
// Serve static files
|
|
22
|
+
server.get('/*', async (req, res) => {
|
|
23
|
+
let requestPath = req.path;
|
|
24
|
+
|
|
25
|
+
// Decode URL
|
|
26
|
+
requestPath = decodeURIComponent(requestPath);
|
|
27
|
+
|
|
28
|
+
// Remove leading slash
|
|
29
|
+
if (requestPath.startsWith('/')) {
|
|
30
|
+
requestPath = requestPath.slice(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Default to index.html if path is empty or ends with /
|
|
34
|
+
if (requestPath === '' || requestPath.endsWith('/')) {
|
|
35
|
+
requestPath = path.join(requestPath, 'index.html');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const filePath = path.join(folderPath, requestPath);
|
|
39
|
+
|
|
40
|
+
// Security: prevent directory traversal
|
|
41
|
+
const resolvedPath = path.resolve(filePath);
|
|
42
|
+
if (!resolvedPath.startsWith(path.resolve(folderPath))) {
|
|
43
|
+
res.status(403).send('Forbidden');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Check if file exists
|
|
49
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
50
|
+
// Try adding index.html if it's a directory
|
|
51
|
+
const indexPath = path.join(resolvedPath, 'index.html');
|
|
52
|
+
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
|
|
53
|
+
return serveFile(indexPath, res, req);
|
|
54
|
+
}
|
|
55
|
+
res.status(404).send('File not found');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stat = fs.statSync(resolvedPath);
|
|
60
|
+
|
|
61
|
+
if (stat.isDirectory()) {
|
|
62
|
+
// Try serving index.html from directory
|
|
63
|
+
const indexPath = path.join(resolvedPath, 'index.html');
|
|
64
|
+
if (fs.existsSync(indexPath)) {
|
|
65
|
+
return serveFile(indexPath, res, req);
|
|
66
|
+
}
|
|
67
|
+
res.status(404).send('File not found');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return serveFile(resolvedPath, res, req);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error('Error:', err.message);
|
|
74
|
+
res.status(500).send('Internal Server Error');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function serveFile(filePath, res, req) {
|
|
79
|
+
const mimeType = mime.lookup(filePath) || 'application/octet-stream';
|
|
80
|
+
const stat = fs.statSync(filePath);
|
|
81
|
+
const fileSize = stat.size;
|
|
82
|
+
|
|
83
|
+
const range = req.headers['range'];
|
|
84
|
+
|
|
85
|
+
if (range) {
|
|
86
|
+
// Handle range requests for video streaming / resume download
|
|
87
|
+
const parts = range.replace(/bytes=/, '').split('-');
|
|
88
|
+
const start = parseInt(parts[0], 10);
|
|
89
|
+
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
|
90
|
+
const chunkSize = end - start + 1;
|
|
91
|
+
|
|
92
|
+
res.status(206);
|
|
93
|
+
res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
|
|
94
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
95
|
+
res.setHeader('Content-Length', chunkSize);
|
|
96
|
+
res.setHeader('Content-Type', mimeType);
|
|
97
|
+
|
|
98
|
+
const stream = fs.createReadStream(filePath, { start, end });
|
|
99
|
+
res.stream(stream);
|
|
100
|
+
} else {
|
|
101
|
+
// Stream full file
|
|
102
|
+
res.setHeader('Content-Length', fileSize);
|
|
103
|
+
res.setHeader('Content-Type', mimeType);
|
|
104
|
+
res.setHeader('Accept-Ranges', 'bytes');
|
|
105
|
+
|
|
106
|
+
const stream = fs.createReadStream(filePath);
|
|
107
|
+
res.stream(stream);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
server.listen(port, '0.0.0.0')
|
|
112
|
+
.then(() => {
|
|
113
|
+
const networkInterfaces = require('os').networkInterfaces();
|
|
114
|
+
const addresses = [];
|
|
115
|
+
|
|
116
|
+
for (const name of Object.keys(networkInterfaces)) {
|
|
117
|
+
for (const net of networkInterfaces[name]) {
|
|
118
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
119
|
+
addresses.push(net.address);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`
|
|
125
|
+
🚀 Laju Server berjalan!
|
|
126
|
+
|
|
127
|
+
📁 Folder: ${folderPath}
|
|
128
|
+
🌐 Local: http://localhost:${port}
|
|
129
|
+
🌐 Network: ${addresses.map(ip => `http://${ip}:${port}`).join('\n 🌐 Network: ') || 'tidak tersedia'}
|
|
130
|
+
|
|
131
|
+
Tekan Ctrl+C untuk berhenti
|
|
132
|
+
`);
|
|
133
|
+
})
|
|
134
|
+
.catch((err) => {
|
|
135
|
+
console.error('❌ Gagal menjalankan server:', err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { startServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "laju-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Instant static file server using hyper-express",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"laju-server": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"static",
|
|
14
|
+
"server",
|
|
15
|
+
"hyper-express",
|
|
16
|
+
"file-server",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"hyper-express": "^6.17.3",
|
|
23
|
+
"mime-types": "^2.1.35"
|
|
24
|
+
}
|
|
25
|
+
}
|