Skip to content

Commit 8e3cb71

Browse files
Add CLI, config file support, and SSL (v0.0.11)
- Add `jss` CLI command with start and init subcommands - Support config file loading (.jss or custom path) - Environment variable support (JSS_PORT, JSS_ROOT, etc.) - HTTPS/SSL support via --ssl-key and --ssl-cert options - Configuration precedence: CLI > env > file > defaults - Update README with CLI documentation
1 parent 5c76e71 commit 8e3cb71

6 files changed

Lines changed: 502 additions & 13 deletions

File tree

README.md

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ npm run benchmark
5454

5555
## Features
5656

57-
### Implemented (v0.0.10)
57+
### Implemented (v0.0.11)
5858

5959
- **LDP CRUD Operations** - GET, PUT, POST, DELETE, HEAD
6060
- **N3 Patch** - Solid's native patch format for RDF updates
6161
- **SPARQL Update** - Standard SPARQL UPDATE protocol for PATCH
6262
- **Conditional Requests** - If-Match/If-None-Match headers (304, 412)
63+
- **CLI & Config** - `jss` command with config file/env var support
64+
- **SSL/TLS** - HTTPS support with certificate configuration
6365
- **WebSocket Notifications** - Real-time updates via solid-0.1 protocol (SolidOS compatible)
6466
- **Container Management** - Create, list, and manage containers
6567
- **Multi-user Pods** - Create pods at `/<username>/`
@@ -92,18 +94,75 @@ npm run benchmark
9294

9395
```bash
9496
npm install
97+
98+
# Or install globally
99+
npm install -g javascript-solid-server
95100
```
96101

97-
### Running
102+
### Quick Start
98103

99104
```bash
100-
# Start server (default port 3000)
101-
npm start
105+
# Initialize configuration (interactive)
106+
jss init
107+
108+
# Start server
109+
jss start
110+
111+
# Or with options
112+
jss start --port 8443 --ssl-key ./key.pem --ssl-cert ./cert.pem
113+
```
114+
115+
### CLI Commands
102116

103-
# Development mode with watch
104-
npm dev
117+
```bash
118+
jss start [options] # Start the server
119+
jss init [options] # Initialize configuration
120+
jss --help # Show help
105121
```
106122

123+
### Start Options
124+
125+
| Option | Description | Default |
126+
|--------|-------------|---------|
127+
| `-p, --port <n>` | Port to listen on | 3000 |
128+
| `-h, --host <addr>` | Host to bind to | 0.0.0.0 |
129+
| `-r, --root <path>` | Data directory | ./data |
130+
| `-c, --config <file>` | Config file path | - |
131+
| `--ssl-key <path>` | SSL private key (PEM) | - |
132+
| `--ssl-cert <path>` | SSL certificate (PEM) | - |
133+
| `--conneg` | Enable Turtle support | false |
134+
| `--notifications` | Enable WebSocket | false |
135+
| `-q, --quiet` | Suppress logs | false |
136+
137+
### Environment Variables
138+
139+
All options can be set via environment variables with `JSS_` prefix:
140+
141+
```bash
142+
export JSS_PORT=8443
143+
export JSS_SSL_KEY=/path/to/key.pem
144+
export JSS_SSL_CERT=/path/to/cert.pem
145+
export JSS_CONNEG=true
146+
jss start
147+
```
148+
149+
### Config File
150+
151+
Create `config.json`:
152+
153+
```json
154+
{
155+
"port": 8443,
156+
"root": "./data",
157+
"sslKey": "./ssl/key.pem",
158+
"sslCert": "./ssl/cert.pem",
159+
"conneg": true,
160+
"notifications": true
161+
}
162+
```
163+
164+
Then: `jss start --config config.json`
165+
107166
### Creating a Pod
108167

109168
```bash

bin/jss.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* JavaScript Solid Server CLI
5+
*
6+
* Usage:
7+
* jss start [options] Start the server
8+
* jss init Initialize configuration
9+
*/
10+
11+
import { Command } from 'commander';
12+
import { createServer } from '../src/server.js';
13+
import { loadConfig, saveConfig, printConfig, defaults } from '../src/config.js';
14+
import fs from 'fs-extra';
15+
import path from 'path';
16+
import { fileURLToPath } from 'url';
17+
import readline from 'readline';
18+
19+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
20+
const pkg = JSON.parse(await fs.readFile(path.join(__dirname, '../package.json'), 'utf8'));
21+
22+
const program = new Command();
23+
24+
program
25+
.name('jss')
26+
.description('JavaScript Solid Server - A minimal, fast, JSON-LD native Solid server')
27+
.version(pkg.version);
28+
29+
/**
30+
* Start command
31+
*/
32+
program
33+
.command('start')
34+
.description('Start the Solid server')
35+
.option('-p, --port <number>', 'Port to listen on', parseInt)
36+
.option('-h, --host <address>', 'Host to bind to')
37+
.option('-r, --root <path>', 'Data directory')
38+
.option('-c, --config <file>', 'Config file path')
39+
.option('--ssl-key <path>', 'Path to SSL private key (PEM)')
40+
.option('--ssl-cert <path>', 'Path to SSL certificate (PEM)')
41+
.option('--multiuser', 'Enable multi-user mode')
42+
.option('--no-multiuser', 'Disable multi-user mode')
43+
.option('--conneg', 'Enable content negotiation (Turtle support)')
44+
.option('--no-conneg', 'Disable content negotiation')
45+
.option('--notifications', 'Enable WebSocket notifications')
46+
.option('--no-notifications', 'Disable WebSocket notifications')
47+
.option('-q, --quiet', 'Suppress log output')
48+
.option('--print-config', 'Print configuration and exit')
49+
.action(async (options) => {
50+
try {
51+
const config = await loadConfig(options, options.config);
52+
53+
if (options.printConfig) {
54+
printConfig(config);
55+
process.exit(0);
56+
}
57+
58+
// Create and start server
59+
const server = createServer({
60+
logger: config.logger,
61+
conneg: config.conneg,
62+
notifications: config.notifications,
63+
ssl: config.ssl ? {
64+
key: await fs.readFile(config.sslKey),
65+
cert: await fs.readFile(config.sslCert),
66+
} : null,
67+
root: config.root,
68+
});
69+
70+
await server.listen({ port: config.port, host: config.host });
71+
72+
const protocol = config.ssl ? 'https' : 'http';
73+
const address = config.host === '0.0.0.0' ? 'localhost' : config.host;
74+
75+
if (!config.quiet) {
76+
console.log(`\n JavaScript Solid Server v${pkg.version}`);
77+
console.log(` ${protocol}://${address}:${config.port}/`);
78+
console.log(`\n Data: ${path.resolve(config.root)}`);
79+
if (config.ssl) console.log(' SSL: enabled');
80+
if (config.conneg) console.log(' Conneg: enabled');
81+
if (config.notifications) console.log(' WebSocket: enabled');
82+
console.log('\n Press Ctrl+C to stop\n');
83+
}
84+
85+
// Handle shutdown
86+
const shutdown = async () => {
87+
if (!config.quiet) console.log('\n Shutting down...');
88+
await server.close();
89+
process.exit(0);
90+
};
91+
92+
process.on('SIGINT', shutdown);
93+
process.on('SIGTERM', shutdown);
94+
95+
} catch (err) {
96+
console.error(`Error: ${err.message}`);
97+
process.exit(1);
98+
}
99+
});
100+
101+
/**
102+
* Init command - interactive configuration
103+
*/
104+
program
105+
.command('init')
106+
.description('Initialize server configuration')
107+
.option('-c, --config <file>', 'Config file path', './config.json')
108+
.option('-y, --yes', 'Accept defaults without prompting')
109+
.action(async (options) => {
110+
const configFile = path.resolve(options.config);
111+
112+
// Check if config already exists
113+
if (await fs.pathExists(configFile)) {
114+
console.log(`Config file already exists: ${configFile}`);
115+
const overwrite = options.yes ? true : await confirm('Overwrite?');
116+
if (!overwrite) {
117+
console.log('Aborted.');
118+
process.exit(0);
119+
}
120+
}
121+
122+
let config;
123+
124+
if (options.yes) {
125+
// Use defaults
126+
config = { ...defaults };
127+
} else {
128+
// Interactive prompts
129+
console.log('\n JavaScript Solid Server Setup\n');
130+
131+
config = {
132+
port: await prompt('Port', defaults.port),
133+
root: await prompt('Data directory', defaults.root),
134+
conneg: await confirm('Enable content negotiation (Turtle support)?', defaults.conneg),
135+
notifications: await confirm('Enable WebSocket notifications?', defaults.notifications),
136+
};
137+
138+
// Ask about SSL
139+
const useSSL = await confirm('Configure SSL?', false);
140+
if (useSSL) {
141+
config.sslKey = await prompt('SSL key path', './ssl/key.pem');
142+
config.sslCert = await prompt('SSL certificate path', './ssl/cert.pem');
143+
}
144+
145+
console.log('');
146+
}
147+
148+
// Save config
149+
await saveConfig(config, configFile);
150+
console.log(`Configuration saved to: ${configFile}`);
151+
152+
// Create data directory
153+
const dataDir = path.resolve(config.root);
154+
await fs.ensureDir(dataDir);
155+
console.log(`Data directory created: ${dataDir}`);
156+
157+
console.log('\nRun `jss start` to start the server.\n');
158+
});
159+
160+
/**
161+
* Helper: Prompt for input
162+
*/
163+
async function prompt(question, defaultValue) {
164+
const rl = readline.createInterface({
165+
input: process.stdin,
166+
output: process.stdout
167+
});
168+
169+
return new Promise((resolve) => {
170+
const defaultStr = defaultValue !== undefined ? ` (${defaultValue})` : '';
171+
rl.question(` ${question}${defaultStr}: `, (answer) => {
172+
rl.close();
173+
const value = answer.trim() || defaultValue;
174+
// Parse numbers
175+
if (typeof defaultValue === 'number' && !isNaN(value)) {
176+
resolve(parseInt(value, 10));
177+
} else {
178+
resolve(value);
179+
}
180+
});
181+
});
182+
}
183+
184+
/**
185+
* Helper: Confirm yes/no
186+
*/
187+
async function confirm(question, defaultValue = false) {
188+
const rl = readline.createInterface({
189+
input: process.stdin,
190+
output: process.stdout
191+
});
192+
193+
return new Promise((resolve) => {
194+
const hint = defaultValue ? '[Y/n]' : '[y/N]';
195+
rl.question(` ${question} ${hint}: `, (answer) => {
196+
rl.close();
197+
const normalized = answer.trim().toLowerCase();
198+
if (normalized === '') {
199+
resolve(defaultValue);
200+
} else {
201+
resolve(normalized === 'y' || normalized === 'yes');
202+
}
203+
});
204+
});
205+
}
206+
207+
// Parse and run
208+
program.parse();

package-lock.json

Lines changed: 17 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
{
22
"name": "javascript-solid-server",
3-
"version": "0.0.10",
3+
"version": "0.0.11",
44
"description": "A minimal, fast Solid server",
55
"main": "src/index.js",
66
"type": "module",
7+
"bin": {
8+
"jss": "./bin/jss.js"
9+
},
710
"repository": {
811
"type": "git",
912
"url": "git+https://github.com/JavaScriptSolidServer/JavaScriptSolidServer.git"
@@ -13,13 +16,14 @@
1316
},
1417
"homepage": "https://github.com/JavaScriptSolidServer/JavaScriptSolidServer#readme",
1518
"scripts": {
16-
"start": "node src/index.js",
17-
"dev": "node --watch src/index.js",
19+
"start": "node bin/jss.js start",
20+
"dev": "node --watch bin/jss.js start",
1821
"test": "node --test --test-concurrency=1",
1922
"benchmark": "node benchmark.js"
2023
},
2124
"dependencies": {
2225
"@fastify/websocket": "^8.3.1",
26+
"commander": "^14.0.2",
2327
"fastify": "^4.25.2",
2428
"fs-extra": "^11.2.0",
2529
"jose": "^6.1.3",

0 commit comments

Comments
 (0)