@jisan901/fs-browser 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/LICENSE +21 -0
- package/README.md +620 -0
- package/bin/withfs.js +276 -0
- package/package.json +67 -0
- package/plugin/fs-handlers.js +419 -0
- package/plugin/vite.js +67 -0
- package/src/index.d.ts +288 -0
- package/src/index.js +402 -0
package/bin/withfs.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* withfs - CLI utility to serve built projects with fs API support
|
|
5
|
+
* Usage: withfs [projectDir] [options]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import fsSync from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { parse as parseUrl } from 'url';
|
|
14
|
+
import { exec } from 'child_process';
|
|
15
|
+
import { createFsHandlers } from '../plugin/fs-handlers.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
// Parse command line arguments
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
let projectDir = '.';
|
|
23
|
+
let host = 'localhost';
|
|
24
|
+
let port = 3000;
|
|
25
|
+
let baseDir = './data';
|
|
26
|
+
let apiPrefix = '/api/fs';
|
|
27
|
+
let openBrowser = false;
|
|
28
|
+
let justFs = false;
|
|
29
|
+
|
|
30
|
+
// Parse arguments
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
|
|
34
|
+
if (arg === '--host' || arg === '-h') {
|
|
35
|
+
const next = args[i + 1];
|
|
36
|
+
if (next && !next.startsWith('-')) {
|
|
37
|
+
host = next;
|
|
38
|
+
i++;
|
|
39
|
+
} else {
|
|
40
|
+
host = '0.0.0.0';
|
|
41
|
+
}
|
|
42
|
+
} else if (arg === '--port' || arg === '-p') {
|
|
43
|
+
port = parseInt(args[++i]) || 3000;
|
|
44
|
+
} else if (arg === '--base-dir' || arg === '-b') {
|
|
45
|
+
baseDir = args[++i] || './data';
|
|
46
|
+
} else if (arg === '--api-prefix' || arg === '-a') {
|
|
47
|
+
apiPrefix = args[++i] || '/api/fs';
|
|
48
|
+
} else if (arg === '--open' || arg === '-o') {
|
|
49
|
+
openBrowser = true;
|
|
50
|
+
} else if (arg === '--justfs') {
|
|
51
|
+
justFs = true;
|
|
52
|
+
} else if (arg === '--help') {
|
|
53
|
+
showHelp();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} else if (!arg.startsWith('-')) {
|
|
56
|
+
projectDir = arg;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Resolve paths
|
|
61
|
+
const PROJECT_DIR = path.resolve(process.cwd(), projectDir);
|
|
62
|
+
const BASE_DIR = path.resolve(PROJECT_DIR, baseDir);
|
|
63
|
+
|
|
64
|
+
// Ensure directories exist
|
|
65
|
+
if (!justFs && !fsSync.existsSync(PROJECT_DIR)) {
|
|
66
|
+
console.error(`Error: Project directory not found: ${PROJECT_DIR}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!fsSync.existsSync(BASE_DIR)) {
|
|
71
|
+
fsSync.mkdirSync(BASE_DIR, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create FS handlers
|
|
75
|
+
const fsHandlers = createFsHandlers(BASE_DIR);
|
|
76
|
+
|
|
77
|
+
// MIME types
|
|
78
|
+
const mimeTypes = {
|
|
79
|
+
'.html': 'text/html',
|
|
80
|
+
'.js': 'application/javascript',
|
|
81
|
+
'.css': 'text/css',
|
|
82
|
+
'.json': 'application/json',
|
|
83
|
+
'.png': 'image/png',
|
|
84
|
+
'.jpg': 'image/jpeg',
|
|
85
|
+
'.jpeg': 'image/jpeg',
|
|
86
|
+
'.gif': 'image/gif',
|
|
87
|
+
'.svg': 'image/svg+xml',
|
|
88
|
+
'.ico': 'image/x-icon',
|
|
89
|
+
'.woff': 'font/woff',
|
|
90
|
+
'.woff2': 'font/woff2',
|
|
91
|
+
'.ttf': 'font/ttf',
|
|
92
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
93
|
+
'.otf': 'font/otf',
|
|
94
|
+
'.txt': 'text/plain',
|
|
95
|
+
'.xml': 'application/xml',
|
|
96
|
+
'.pdf': 'application/pdf',
|
|
97
|
+
'.zip': 'application/zip',
|
|
98
|
+
'.wasm': 'application/wasm'
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// Serve static files
|
|
104
|
+
async function serveStaticFile(req, res) {
|
|
105
|
+
const parsedUrl = parseUrl(req.url);
|
|
106
|
+
let pathname = parsedUrl.pathname;
|
|
107
|
+
|
|
108
|
+
// Default to index.html
|
|
109
|
+
if (pathname === '/') {
|
|
110
|
+
pathname = '/index.html';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const filePath = path.join(PROJECT_DIR, pathname);
|
|
114
|
+
|
|
115
|
+
// Security check
|
|
116
|
+
if (!filePath.startsWith(PROJECT_DIR)) {
|
|
117
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
118
|
+
res.end('Forbidden');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const stats = await fs.stat(filePath);
|
|
124
|
+
|
|
125
|
+
if (stats.isDirectory()) {
|
|
126
|
+
const indexPath = path.join(filePath, 'index.html');
|
|
127
|
+
if (fsSync.existsSync(indexPath)) {
|
|
128
|
+
const content = await fs.readFile(indexPath);
|
|
129
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
130
|
+
res.end(content);
|
|
131
|
+
} else {
|
|
132
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
133
|
+
res.end('Not Found');
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const ext = path.extname(filePath);
|
|
139
|
+
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
|
140
|
+
|
|
141
|
+
const content = await fs.readFile(filePath);
|
|
142
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
143
|
+
res.end(content);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (err.code === 'ENOENT') {
|
|
146
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
147
|
+
res.end('Not Found');
|
|
148
|
+
} else {
|
|
149
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
150
|
+
res.end('Internal Server Error');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Create server
|
|
156
|
+
const server = http.createServer(async (req, res) => {
|
|
157
|
+
// CORS headers
|
|
158
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
159
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
160
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
161
|
+
|
|
162
|
+
if (req.method === 'OPTIONS') {
|
|
163
|
+
res.writeHead(200);
|
|
164
|
+
res.end();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
// Handle FS API routes
|
|
170
|
+
if (req.url.startsWith(apiPrefix)) {
|
|
171
|
+
const routePath = req.url.substring(apiPrefix.length) || '/';
|
|
172
|
+
const handlerKey = `${req.method} ${routePath.split('?')[0]}`;
|
|
173
|
+
const handler = fsHandlers[handlerKey];
|
|
174
|
+
|
|
175
|
+
if (handler) {
|
|
176
|
+
await handler(req, res);
|
|
177
|
+
} else {
|
|
178
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
179
|
+
res.end(JSON.stringify({ error: 'Route not found' }));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Serve static files (only if not in justFs mode)
|
|
183
|
+
else if (!justFs) {
|
|
184
|
+
await serveStaticFile(req, res);
|
|
185
|
+
}
|
|
186
|
+
// In justFs mode, reject non-API requests
|
|
187
|
+
else {
|
|
188
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
189
|
+
res.end(JSON.stringify({
|
|
190
|
+
error: 'Not Found',
|
|
191
|
+
message: 'Running in --justfs mode. Only fs API endpoints are available.'
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Server error:', error);
|
|
196
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
197
|
+
res.end(JSON.stringify({
|
|
198
|
+
error: {
|
|
199
|
+
message: error.message,
|
|
200
|
+
status: 500
|
|
201
|
+
}
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Start server
|
|
207
|
+
server.listen(port, host, () => {
|
|
208
|
+
console.log('\n┌─────────────────────────────────────────┐');
|
|
209
|
+
console.log(`│ 🚀 withfs - ${justFs ? 'FS API Server' : 'Server Running'} │`);
|
|
210
|
+
console.log('└─────────────────────────────────────────┘\n');
|
|
211
|
+
if (!justFs) {
|
|
212
|
+
console.log(` Project: ${PROJECT_DIR}`);
|
|
213
|
+
}
|
|
214
|
+
console.log(` Local: http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`);
|
|
215
|
+
if (host === '0.0.0.0') {
|
|
216
|
+
console.log(` Network: http://<your-ip>:${port}`);
|
|
217
|
+
}
|
|
218
|
+
console.log(` FS API: ${apiPrefix}`);
|
|
219
|
+
console.log(` Base Dir: ${BASE_DIR}`);
|
|
220
|
+
if (justFs) {
|
|
221
|
+
console.log(` Mode: Just FS (no static files)`);
|
|
222
|
+
}
|
|
223
|
+
console.log('\n Press Ctrl+C to stop\n');
|
|
224
|
+
|
|
225
|
+
if (openBrowser && !justFs) {
|
|
226
|
+
const url = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
|
|
227
|
+
const start = process.platform === 'darwin' ? 'open' :
|
|
228
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
229
|
+
exec(`${start} ${url}`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Help text
|
|
234
|
+
function showHelp() {
|
|
235
|
+
console.log(`
|
|
236
|
+
withfs - Serve built projects with fs API support
|
|
237
|
+
|
|
238
|
+
USAGE:
|
|
239
|
+
withfs [projectDir] [options]
|
|
240
|
+
|
|
241
|
+
OPTIONS:
|
|
242
|
+
--host, -h [host] Host to bind to (default: localhost, --host alone = 0.0.0.0)
|
|
243
|
+
--port, -p <port> Port to use (default: 3000)
|
|
244
|
+
--base-dir, -b <dir> Base directory for fs operations (default: ./data)
|
|
245
|
+
--api-prefix, -a <prefix> API route prefix (default: /api/fs)
|
|
246
|
+
--open, -o Open browser automatically
|
|
247
|
+
--justfs Only run fs API (no static file serving)
|
|
248
|
+
--help Show this help message
|
|
249
|
+
|
|
250
|
+
EXAMPLES:
|
|
251
|
+
withfs # Serve current directory
|
|
252
|
+
withfs ./dist # Serve dist directory
|
|
253
|
+
withfs --host # Serve on all interfaces (0.0.0.0)
|
|
254
|
+
withfs --port 8080 # Use custom port
|
|
255
|
+
withfs --open # Open browser automatically
|
|
256
|
+
withfs --justfs # Only fs API, no static files
|
|
257
|
+
withfs --justfs --port 5001 # Dedicated fs API server
|
|
258
|
+
|
|
259
|
+
AFTER BUILD WORKFLOW:
|
|
260
|
+
npm run build
|
|
261
|
+
withfs ./dist --host --open
|
|
262
|
+
|
|
263
|
+
JUST FS MODE:
|
|
264
|
+
# Run only fs API without static file serving
|
|
265
|
+
withfs --justfs --port 5001
|
|
266
|
+
# Useful for microservices or separate frontend servers
|
|
267
|
+
`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Handle graceful shutdown
|
|
271
|
+
process.on('SIGINT', () => {
|
|
272
|
+
console.log('\n\n 👋 Shutting down server...\n');
|
|
273
|
+
server.close(() => {
|
|
274
|
+
process.exit(0);
|
|
275
|
+
});
|
|
276
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jisan901/fs-browser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Browser-compatible filesystem API with Vite plugin support",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"withfs": "bin/withfs.js"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/index.d.ts",
|
|
17
|
+
"import": "./src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./plugin": {
|
|
20
|
+
"import": "./plugin/vite.js",
|
|
21
|
+
"require": "./plugin/vite.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"src",
|
|
27
|
+
"plugin",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"filesystem",
|
|
36
|
+
"browser",
|
|
37
|
+
"fs",
|
|
38
|
+
"vite",
|
|
39
|
+
"plugin",
|
|
40
|
+
"file-api",
|
|
41
|
+
"node-fs",
|
|
42
|
+
"browser-fs",
|
|
43
|
+
"cli",
|
|
44
|
+
"server"
|
|
45
|
+
],
|
|
46
|
+
"author": "claude:sonnet4.5",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/Jisan901/fs-browser.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/Jisan901/fs-browser/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/Jisan901/fs-browser#readme",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"vite": "^4.0.0 || ^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"vite": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=16.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|