javascript-solid-server 0.0.164 → 0.0.166

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/jsserve/README.md DELETED
@@ -1,194 +0,0 @@
1
- # servejss
2
-
3
- > Static file server with REST write support. A drop-in `npx serve` alternative.
4
-
5
- [![npm version](https://img.shields.io/npm/v/servejss.svg)](https://www.npmjs.com/package/servejss)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
-
8
- ## Why?
9
-
10
- `npx serve` is great for quickly serving static files, but it's **read-only**. Sometimes you need to:
11
-
12
- - Upload files during development
13
- - Test REST APIs locally
14
- - Sync files between devices on your LAN
15
- - Have a simple WebDAV-like server
16
-
17
- **servejss** is `serve` with superpowers: same simple interface, but you can write too.
18
-
19
- ## Install
20
-
21
- ```bash
22
- npm install -g servejss
23
- ```
24
-
25
- Or use directly with npx:
26
-
27
- ```bash
28
- npx servejss
29
- ```
30
-
31
- ## Usage
32
-
33
- ```bash
34
- # Serve current directory (read + write enabled)
35
- servejss
36
-
37
- # Serve specific directory
38
- servejss ./public
39
-
40
- # Custom port
41
- servejss -p 8080
42
-
43
- # Specify port and directory
44
- servejss -l 3000 ./dist
45
-
46
- # Read-only mode (exactly like npx serve)
47
- servejss --read-only
48
- ```
49
-
50
- ## Output
51
-
52
- ```
53
- servejss
54
-
55
- Directory: /home/user/project
56
-
57
- Local: http://localhost:3000
58
- Network: http://192.168.1.5:3000
59
-
60
- Mode: GET/PUT/DELETE enabled
61
-
62
- Press Ctrl+C to stop
63
- ```
64
-
65
- ## REST API
66
-
67
- ```bash
68
- # Read a file
69
- curl http://localhost:3000/file.txt
70
-
71
- # Create or update a file
72
- curl -X PUT -d "Hello, World!" http://localhost:3000/file.txt
73
-
74
- # Delete a file
75
- curl -X DELETE http://localhost:3000/file.txt
76
-
77
- # Conditional update (only if ETag matches)
78
- curl -X PUT -H 'If-Match: "abc123"' -d "Updated" http://localhost:3000/file.txt
79
-
80
- # Create only if doesn't exist
81
- curl -X PUT -H 'If-None-Match: *' -d "New file" http://localhost:3000/new.txt
82
- ```
83
-
84
- ## Options
85
-
86
- ```
87
- Usage: servejss [options] [directory]
88
-
89
- Options:
90
- -v, --version Output version number
91
- -l, --listen <uri> Specify a URI endpoint on which to listen
92
- -p, --port <port> Specify custom port (default: 3000)
93
- -H, --host <host> Host to bind to (default: 0.0.0.0)
94
- -s, --single Rewrite all not-found requests to index.html (SPA mode)
95
- -d, --debug Show debugging information
96
- -C, --cors Enable CORS (enabled by default)
97
- -L, --no-request-logging Do not log any request information
98
- --no-etag Disable ETag generation
99
- -S, --symlinks Resolve symlinks instead of showing 404
100
- --ssl-cert <path> Path to SSL certificate
101
- --ssl-key <path> Path to SSL private key
102
- --no-port-switching Do not open a different port if specified one is taken
103
- -r, --read-only Disable PUT/DELETE methods (like npx serve)
104
- --auth <credentials> Enable basic auth (user:pass)
105
- --solid Enable full Solid protocol features
106
- -q, --quiet Suppress all output
107
- -h, --help Display help
108
- ```
109
-
110
- ## Comparison with serve
111
-
112
- | Feature | serve | servejss |
113
- |---------|-------|---------|
114
- | Static file serving | ✅ | ✅ |
115
- | Directory listings | ✅ | ✅ |
116
- | CORS | ✅ | ✅ |
117
- | SPA mode | ✅ | ✅ |
118
- | Custom port | ✅ | ✅ |
119
- | Auto port switching | ✅ | ✅ |
120
- | SSL/TLS | ✅ | ✅ |
121
- | **PUT (create/update)** | ❌ | ✅ |
122
- | **DELETE** | ❌ | ✅ |
123
- | **ETags** | ❌ | ✅ |
124
- | **Conditional requests** | ❌ | ✅ |
125
- | **Upgrade to Solid** | ❌ | ✅ |
126
-
127
- ## Advanced Features
128
-
129
- ### Conditional Requests
130
-
131
- servejss supports ETags for efficient caching and safe concurrent updates:
132
-
133
- ```bash
134
- # Get a file with its ETag
135
- curl -i http://localhost:3000/file.txt
136
- # Returns: ETag: "a1b2c3"
137
-
138
- # Only fetch if changed
139
- curl -H 'If-None-Match: "a1b2c3"' http://localhost:3000/file.txt
140
- # Returns: 304 Not Modified (if unchanged)
141
-
142
- # Safe update (fails if file changed since you read it)
143
- curl -X PUT -H 'If-Match: "a1b2c3"' -d "new content" http://localhost:3000/file.txt
144
- ```
145
-
146
- ### Upgrade to Solid
147
-
148
- servejss is powered by [JSS (JavaScript Solid Server)](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer). Enable full Solid protocol support:
149
-
150
- ```bash
151
- servejss --solid
152
- ```
153
-
154
- This enables:
155
- - Solid-OIDC authentication
156
- - Web Access Control (WAC)
157
- - Linked Data support (Turtle, JSON-LD)
158
- - WebID profiles
159
-
160
- ## Use Cases
161
-
162
- ### Local Development Server
163
- ```bash
164
- # Serve your project with write support for uploads
165
- cd my-project
166
- servejss
167
- ```
168
-
169
- ### Quick File Sharing on LAN
170
- ```bash
171
- # Share files with devices on your network
172
- servejss --read-only ~/shared-files
173
- ```
174
-
175
- ### REST API Testing
176
- ```bash
177
- # Mock a simple REST backend
178
- servejss ./mock-data
179
- ```
180
-
181
- ### WebDAV Alternative
182
- ```bash
183
- # Lightweight file sync server
184
- servejss --auth user:pass ~/sync
185
- ```
186
-
187
- ## License
188
-
189
- MIT
190
-
191
- ## Related Projects
192
-
193
- - [JSS](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer) - Full Solid server
194
- - [serve](https://github.com/vercel/serve) - Static file serving (read-only)
@@ -1,329 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * servejss - Static file server with REST write support
5
- * A drop-in replacement for `npx serve` with PUT/DELETE capabilities
6
- */
7
-
8
- import { program } from 'commander';
9
- import { resolve } from 'path';
10
- import { spawn } from 'child_process';
11
- import { createServer } from 'net';
12
- import { networkInterfaces } from 'os';
13
- import { fileURLToPath } from 'url';
14
- import { dirname, join } from 'path';
15
- import chalk from 'chalk';
16
- import fs from 'fs';
17
-
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
- const pkg = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
21
-
22
- /**
23
- * Check if a port is available
24
- * @param {number} port
25
- * @returns {Promise<boolean>}
26
- */
27
- function isPortAvailable(port) {
28
- return new Promise((resolve) => {
29
- const server = createServer();
30
- server.once('error', () => resolve(false));
31
- server.once('listening', () => {
32
- server.close();
33
- resolve(true);
34
- });
35
- server.listen(port, '0.0.0.0');
36
- });
37
- }
38
-
39
- /**
40
- * Find an available port starting from the given port
41
- * @param {number} startPort
42
- * @param {number} maxAttempts
43
- * @returns {Promise<number>}
44
- */
45
- async function findAvailablePort(startPort, maxAttempts = 100) {
46
- for (let i = 0; i < maxAttempts; i++) {
47
- const port = startPort + i;
48
- if (await isPortAvailable(port)) {
49
- return port;
50
- }
51
- }
52
- throw new Error(`Could not find available port after ${maxAttempts} attempts`);
53
- }
54
-
55
- /**
56
- * Get local network IP address
57
- * @returns {string|null}
58
- */
59
- function getNetworkAddress() {
60
- const nets = networkInterfaces();
61
- for (const name of Object.keys(nets)) {
62
- for (const net of nets[name]) {
63
- if (net.family === 'IPv4' && !net.internal) {
64
- return net.address;
65
- }
66
- }
67
- }
68
- return null;
69
- }
70
-
71
- /**
72
- * Print the startup banner
73
- */
74
- function printBanner(options) {
75
- const { port, host, directory, readOnly, live, networkAddress } = options;
76
-
77
- const local = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
78
- const network = networkAddress ? `http://${networkAddress}:${port}` : null;
79
-
80
- const mode = readOnly ? 'GET only (read-only)' : 'GET/PUT/DELETE enabled';
81
-
82
- console.log();
83
- console.log(chalk.bgCyan.black(' servejss '));
84
- console.log();
85
- console.log(` ${chalk.gray('Directory:')} ${directory}`);
86
- console.log();
87
- console.log(` ${chalk.gray('Local:')} ${chalk.cyan(local)}`);
88
- if (network) {
89
- console.log(` ${chalk.gray('Network:')} ${chalk.cyan(network)}`);
90
- }
91
- console.log();
92
- console.log(` ${chalk.gray('Mode:')} ${chalk.yellow(mode)}`);
93
- if (live) {
94
- console.log(` ${chalk.gray('Live:')} ${chalk.green('Watching for changes')}`);
95
- }
96
- console.log();
97
- console.log(chalk.gray(' Press Ctrl+C to stop'));
98
- console.log();
99
- }
100
-
101
- /**
102
- * Print an error message
103
- */
104
- function printError(message) {
105
- console.error();
106
- console.error(chalk.red(' ERROR:'), message);
107
- console.error();
108
- }
109
-
110
- // CLI definition - matching `serve` interface
111
- program
112
- .name('servejss')
113
- .description('Static file server with REST write support')
114
- .version(pkg.version, '-v, --version')
115
- .argument('[directory]', 'Directory to serve', '.')
116
- .option('-l, --listen <uri>', 'Specify a URI endpoint on which to listen')
117
- .option('-p, --port <port>', 'Specify custom port', '3000')
118
- .option('-H, --host <host>', 'Host to bind to', '0.0.0.0')
119
- .option('-s, --single', 'Rewrite all not-found requests to index.html (SPA mode)')
120
- .option('-d, --debug', 'Show debugging information')
121
- .option('-C, --cors', 'Enable CORS (enabled by default)')
122
- .option('-L, --no-request-logging', 'Do not log any request information')
123
- .option('--no-etag', 'Disable ETag generation')
124
- .option('-S, --symlinks', 'Resolve symlinks instead of showing 404')
125
- .option('--ssl-cert <path>', 'Path to SSL certificate')
126
- .option('--ssl-key <path>', 'Path to SSL private key')
127
- .option('--no-port-switching', 'Do not open a different port if specified one is taken')
128
- // servejss-specific options
129
- .option('-r, --read-only', 'Disable PUT/DELETE methods (like npx serve)')
130
- .option('--write', 'Enable PUT/DELETE methods (default)')
131
- .option('--auth <credentials>', 'Enable basic auth (user:pass)')
132
- .option('--solid', 'Enable full Solid protocol features')
133
- .option('--live', 'Enable live reload (default: true)')
134
- .option('--no-live', 'Disable live reload')
135
- .option('--git', 'Enable git HTTP backend (default: true)')
136
- .option('--no-git', 'Disable git HTTP backend')
137
- .option('-q, --quiet', 'Suppress all output')
138
- .addHelpText('after', `
139
- Examples:
140
- $ servejss Serve current directory with read/write
141
- $ servejss ./public Serve specific directory
142
- $ servejss -p 8080 Use custom port
143
- $ servejss --read-only Read-only mode (like npx serve)
144
- $ servejss -l 3000 ./dist Listen on port 3000, serve ./dist
145
-
146
- REST API:
147
- GET /file.txt Read file
148
- PUT /file.txt Create/update file
149
- DELETE /file.txt Delete file
150
- HEAD /file.txt Get file metadata
151
-
152
- Differences from 'serve':
153
- - PUT and DELETE enabled by default (use --read-only to disable)
154
- - ETag support for caching and conditional requests
155
- - Optional upgrade to full Solid protocol with --solid
156
- `)
157
- .action(async (directory, options) => {
158
- try {
159
- await run(directory, options);
160
- } catch (err) {
161
- printError(err.message);
162
- process.exit(1);
163
- }
164
- });
165
-
166
- program.parse();
167
-
168
- /**
169
- * Main run function
170
- */
171
- async function run(directory, options) {
172
- // Resolve directory
173
- const dir = resolve(directory || '.');
174
-
175
- // Check directory exists
176
- if (!fs.existsSync(dir)) {
177
- throw new Error(`Directory not found: ${dir}`);
178
- }
179
-
180
- if (!fs.statSync(dir).isDirectory()) {
181
- throw new Error(`Not a directory: ${dir}`);
182
- }
183
-
184
- // Parse port from --listen or --port
185
- let port = parseInt(options.port, 10);
186
- if (options.listen) {
187
- // Parse listen URI (e.g., "3000", "tcp://localhost:3000")
188
- const listenMatch = options.listen.match(/:?(\d+)$/);
189
- if (listenMatch) {
190
- port = parseInt(listenMatch[1], 10);
191
- }
192
- }
193
-
194
- // Find available port if needed
195
- const originalPort = port;
196
- if (options.portSwitching !== false) {
197
- const available = await isPortAvailable(port);
198
- if (!available) {
199
- port = await findAvailablePort(port + 1);
200
- if (!options.quiet) {
201
- console.log(chalk.yellow(` Port ${originalPort} is in use, using ${port} instead`));
202
- }
203
- }
204
- } else {
205
- const available = await isPortAvailable(port);
206
- if (!available) {
207
- throw new Error(`Port ${port} is already in use`);
208
- }
209
- }
210
-
211
- // Build JSS arguments
212
- const jssArgs = [
213
- 'start',
214
- '--root', dir,
215
- '--port', String(port),
216
- '--host', options.host,
217
- '--public', // Enable open access (requires #107)
218
- ];
219
-
220
- // Read-only mode
221
- if (options.readOnly) {
222
- jssArgs.push('--read-only');
223
- }
224
-
225
- // Quiet mode
226
- if (options.quiet || !options.requestLogging) {
227
- jssArgs.push('--quiet');
228
- }
229
-
230
- // SSL
231
- if (options.sslCert && options.sslKey) {
232
- jssArgs.push('--ssl-cert', options.sslCert);
233
- jssArgs.push('--ssl-key', options.sslKey);
234
- }
235
-
236
- // Solid mode (full features)
237
- if (options.solid) {
238
- // Remove --public, use full Solid auth
239
- const publicIndex = jssArgs.indexOf('--public');
240
- if (publicIndex > -1) {
241
- jssArgs.splice(publicIndex, 1);
242
- }
243
- jssArgs.push('--idp');
244
- jssArgs.push('--conneg');
245
- }
246
-
247
- // Live reload mode (default: enabled)
248
- if (options.live !== false) {
249
- jssArgs.push('--live-reload');
250
- }
251
-
252
- // Git HTTP backend (default: enabled)
253
- if (options.git !== false) {
254
- jssArgs.push('--git');
255
- }
256
-
257
- // Debug mode
258
- if (options.debug) {
259
- console.log(chalk.gray(' JSS args:'), jssArgs.join(' '));
260
- }
261
-
262
- // Print banner
263
- if (!options.quiet) {
264
- printBanner({
265
- port,
266
- host: options.host,
267
- directory: dir,
268
- readOnly: options.readOnly,
269
- live: options.live !== false,
270
- networkAddress: getNetworkAddress(),
271
- });
272
- }
273
-
274
- // Find jss binary
275
- let jssPath;
276
- try {
277
- // Try to find jss in node_modules
278
- const jssModule = await import.meta.resolve('javascript-solid-server/bin/jss.js');
279
- jssPath = fileURLToPath(jssModule);
280
- } catch {
281
- // Fallback to global jss
282
- jssPath = 'jss';
283
- }
284
-
285
- // Spawn JSS
286
- const child = spawn('node', [jssPath, ...jssArgs], {
287
- stdio: options.quiet ? 'ignore' : ['inherit', 'pipe', 'inherit'],
288
- env: { ...process.env, FORCE_COLOR: '1' },
289
- });
290
-
291
- // Filter JSS output to remove its banner (we have our own)
292
- if (!options.quiet && child.stdout) {
293
- let skipBanner = true;
294
- child.stdout.on('data', (data) => {
295
- const str = data.toString();
296
- // Skip JSS startup banner, show rest
297
- if (skipBanner && (str.includes('Listening') || str.includes('Server'))) {
298
- skipBanner = false;
299
- return;
300
- }
301
- if (!skipBanner || options.debug) {
302
- process.stdout.write(data);
303
- }
304
- });
305
- }
306
-
307
- // Handle child process errors
308
- child.on('error', (err) => {
309
- printError(`Failed to start server: ${err.message}`);
310
- process.exit(1);
311
- });
312
-
313
- child.on('exit', (code) => {
314
- if (code !== 0 && code !== null) {
315
- process.exit(code);
316
- }
317
- });
318
-
319
- // Handle graceful shutdown
320
- const shutdown = () => {
321
- if (!options.quiet) {
322
- console.log(chalk.gray('\n Shutting down...'));
323
- }
324
- child.kill('SIGTERM');
325
- };
326
-
327
- process.on('SIGINT', shutdown);
328
- process.on('SIGTERM', shutdown);
329
- }