@scriptdb/cli 1.0.8 → 1.1.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 +78 -22
- package/dist/index.js +40 -33
- package/dist/workers/bcrypt-worker.js +12 -0
- package/dist/workers/validator-worker.js +82 -0
- package/dist/workers/vm-worker.js +25 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -4,31 +4,20 @@ Command-line interface for ScriptDB - Database management with TypeScript script
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Pre-built binaries are available for download:
|
|
10
|
-
|
|
11
|
-
- **Linux x64**: `scriptdb-linux-x64`
|
|
12
|
-
- **Linux ARM64**: `scriptdb-linux-arm64`
|
|
13
|
-
- **Linux x64 (musl)**: `scriptdb-linux-x64-musl`
|
|
14
|
-
- **Linux ARM64 (musl)**: `scriptdb-linux-arm64-musl`
|
|
15
|
-
- **macOS x64 (Intel)**: `scriptdb-darwin-x64`
|
|
16
|
-
- **macOS ARM64 (Apple Silicon)**: `scriptdb-darwin-arm64`
|
|
17
|
-
- **Windows x64**: `scriptdb-windows-x64.exe`
|
|
18
|
-
|
|
19
|
-
**Note**: These binaries are standalone info-only builds. They show help/version but don't include server functionality. For full functionality, install from source.
|
|
20
|
-
|
|
21
|
-
Make it executable (Linux/macOS):
|
|
7
|
+
Install from npm:
|
|
22
8
|
```bash
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
npm install -g @scriptdb/cli
|
|
10
|
+
# or
|
|
11
|
+
bun add -g @scriptdb/cli
|
|
25
12
|
```
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
Install from source:
|
|
29
15
|
```bash
|
|
16
|
+
git clone <repo>
|
|
17
|
+
cd scriptdb
|
|
30
18
|
bun install
|
|
31
|
-
bun run build
|
|
19
|
+
bun run build
|
|
20
|
+
npm link
|
|
32
21
|
```
|
|
33
22
|
|
|
34
23
|
## Usage
|
|
@@ -109,6 +98,55 @@ Example:
|
|
|
109
98
|
}
|
|
110
99
|
```
|
|
111
100
|
|
|
101
|
+
### Package Management
|
|
102
|
+
|
|
103
|
+
Install packages to ScriptDB:
|
|
104
|
+
```bash
|
|
105
|
+
# Install to ~/.scriptdb/packages
|
|
106
|
+
scriptdb add lodash
|
|
107
|
+
scriptdb add axios express
|
|
108
|
+
|
|
109
|
+
# Install to current directory
|
|
110
|
+
scriptdb add --local lodash
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Remove packages:
|
|
114
|
+
```bash
|
|
115
|
+
# Remove from ~/.scriptdb/packages
|
|
116
|
+
scriptdb remove lodash
|
|
117
|
+
|
|
118
|
+
# Remove from current directory
|
|
119
|
+
scriptdb remove --local lodash
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Monitoring
|
|
123
|
+
|
|
124
|
+
View real-time logs:
|
|
125
|
+
```bash
|
|
126
|
+
scriptdb logs
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Monitor performance (CPU, memory, uptime):
|
|
130
|
+
```bash
|
|
131
|
+
scriptdb monit
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Commands
|
|
135
|
+
|
|
136
|
+
| Command | Description |
|
|
137
|
+
|---------|-------------|
|
|
138
|
+
| `scriptdb start [-d]` | Start server (`-d` for daemon/PM2 mode) |
|
|
139
|
+
| `scriptdb stop` | Stop the running server |
|
|
140
|
+
| `scriptdb restart [-d]` | Restart the server |
|
|
141
|
+
| `scriptdb status` | Check server status and view metrics |
|
|
142
|
+
| `scriptdb logs` | View real-time logs |
|
|
143
|
+
| `scriptdb monit` | Monitor performance (CPU, memory, uptime) |
|
|
144
|
+
| `scriptdb shell` | Start interactive shell |
|
|
145
|
+
| `scriptdb add <pkg>` | Install packages to ~/.scriptdb |
|
|
146
|
+
| `scriptdb add --local <pkg>` | Install packages to current directory |
|
|
147
|
+
| `scriptdb remove <pkg>` | Remove packages from ~/.scriptdb |
|
|
148
|
+
| `scriptdb remove --local <pkg>` | Remove packages from current directory |
|
|
149
|
+
|
|
112
150
|
## Building
|
|
113
151
|
|
|
114
152
|
### Build for all platforms (local)
|
|
@@ -145,10 +183,28 @@ bun run dev
|
|
|
145
183
|
|
|
146
184
|
## Files
|
|
147
185
|
|
|
148
|
-
- PID file: `~/.scriptdb/scriptdb.pid`
|
|
149
|
-
- Log file: `~/.scriptdb/scriptdb.log`
|
|
150
186
|
- Config file: `~/.scriptdb/config.json`
|
|
187
|
+
- Ecosystem config: `~/.scriptdb/ecosystem.config.js` (PM2)
|
|
188
|
+
- PM2 logs: `~/.scriptdb/pm2-*.log`
|
|
151
189
|
- Databases: `~/.scriptdb/databases/`
|
|
190
|
+
- Packages: `~/.scriptdb/packages/`
|
|
191
|
+
|
|
192
|
+
## Changelog
|
|
193
|
+
|
|
194
|
+
### 1.1.0 (2025-01-16)
|
|
195
|
+
|
|
196
|
+
**Added**
|
|
197
|
+
- Native `scriptdb logs` command to view real-time logs
|
|
198
|
+
- Native `scriptdb monit` command to monitor performance
|
|
199
|
+
- Native `scriptdb restart` command to restart the server
|
|
200
|
+
- Native `scriptdb stop` command to stop the server
|
|
201
|
+
- Dynamic version reading from package.json
|
|
202
|
+
- ESLint configuration for TypeScript linting
|
|
203
|
+
|
|
204
|
+
**Fixed**
|
|
205
|
+
- Fixed TypeScript module resolution errors
|
|
206
|
+
- Fixed test command to continue gracefully when packages have no tests
|
|
207
|
+
- Improved error handling and Windows compatibility
|
|
152
208
|
|
|
153
209
|
## License
|
|
154
210
|
|
package/dist/index.js
CHANGED
|
@@ -93810,6 +93810,7 @@ class Protocal {
|
|
|
93810
93810
|
IP_FAIL_WINDOW_MS;
|
|
93811
93811
|
MAX_LOGIN_ATTEMPTS;
|
|
93812
93812
|
LOCK_DURATION_MS;
|
|
93813
|
+
ENABLE_IP_LOCKOUT;
|
|
93813
93814
|
MAX_MESSAGE_BYTES;
|
|
93814
93815
|
MAX_MESSAGES_PER_CONNECTION;
|
|
93815
93816
|
CONNECTION_TIMEOUT_MS;
|
|
@@ -93923,9 +93924,10 @@ class Protocal {
|
|
|
93923
93924
|
});
|
|
93924
93925
|
this.loginAttemptCache = new Map;
|
|
93925
93926
|
this.ipAttemptCache = new Map;
|
|
93926
|
-
this.IP_FAIL_WINDOW_MS = options2.ipFailWindowMs || 15 * 60 * 1000;
|
|
93927
|
-
this.MAX_LOGIN_ATTEMPTS = options2.maxLoginAttempts || 5;
|
|
93928
|
-
this.LOCK_DURATION_MS = options2.lockDurationMs || 15 * 60 * 1000;
|
|
93927
|
+
this.IP_FAIL_WINDOW_MS = options2.ipFailWindowMs || fileConfig.ipFailWindowMs || 15 * 60 * 1000;
|
|
93928
|
+
this.MAX_LOGIN_ATTEMPTS = options2.maxLoginAttempts || fileConfig.maxLoginAttempts || 5;
|
|
93929
|
+
this.LOCK_DURATION_MS = options2.lockDurationMs || fileConfig.lockDurationMs || 15 * 60 * 1000;
|
|
93930
|
+
this.ENABLE_IP_LOCKOUT = options2.enableIpLockout !== undefined ? options2.enableIpLockout : fileConfig.enableIpLockout !== undefined ? fileConfig.enableIpLockout : true;
|
|
93929
93931
|
this.MAX_MESSAGE_BYTES = options2.maxMessageBytes || 64 * 1024;
|
|
93930
93932
|
this.MAX_MESSAGES_PER_CONNECTION = options2.maxMessagesPerConnection || 1000;
|
|
93931
93933
|
this.CONNECTION_TIMEOUT_MS = typeof options2.connectionTimeoutMs === "number" ? options2.connectionTimeoutMs : typeof fileConfig.connectionTimeoutMs === "number" ? fileConfig.connectionTimeoutMs : 0;
|
|
@@ -94371,32 +94373,34 @@ class Protocal {
|
|
|
94371
94373
|
err: e && e.message || String(e)
|
|
94372
94374
|
});
|
|
94373
94375
|
}
|
|
94374
|
-
|
|
94375
|
-
|
|
94376
|
-
|
|
94377
|
-
|
|
94378
|
-
|
|
94379
|
-
|
|
94380
|
-
|
|
94381
|
-
|
|
94382
|
-
|
|
94383
|
-
ipRec.
|
|
94384
|
-
|
|
94385
|
-
ip
|
|
94386
|
-
|
|
94387
|
-
|
|
94388
|
-
}
|
|
94389
|
-
this.ipAttemptCache.set(remoteIP, ipRec);
|
|
94390
|
-
if (ipRec.lockedUntil && ipRec.lockedUntil > nowTs) {
|
|
94391
|
-
this.audit("ip.locked", { ip: remoteIP });
|
|
94392
|
-
try {
|
|
94393
|
-
sendWithBackpressure({
|
|
94394
|
-
command: "login",
|
|
94395
|
-
message: "LOCKED_IP",
|
|
94396
|
-
data: null
|
|
94376
|
+
if (this.ENABLE_IP_LOCKOUT) {
|
|
94377
|
+
const nowTs = Date.now();
|
|
94378
|
+
const ipRec = this.ipAttemptCache.get(remoteIP) || {
|
|
94379
|
+
attempts: 0,
|
|
94380
|
+
lockedUntil: 0,
|
|
94381
|
+
expiresAt: nowTs + this._attemptCacheTTL
|
|
94382
|
+
};
|
|
94383
|
+
ipRec.attempts = (ipRec.attempts || 0) + 1;
|
|
94384
|
+
ipRec.expiresAt = nowTs + this._attemptCacheTTL;
|
|
94385
|
+
if (ipRec.attempts >= this.MAX_LOGIN_ATTEMPTS) {
|
|
94386
|
+
ipRec.lockedUntil = nowTs + this.LOCK_DURATION_MS;
|
|
94387
|
+
this.audit("ip.lockout", {
|
|
94388
|
+
ip: remoteIP,
|
|
94389
|
+
attempts: ipRec.attempts
|
|
94397
94390
|
});
|
|
94398
|
-
}
|
|
94399
|
-
|
|
94391
|
+
}
|
|
94392
|
+
this.ipAttemptCache.set(remoteIP, ipRec);
|
|
94393
|
+
if (ipRec.lockedUntil && ipRec.lockedUntil > nowTs) {
|
|
94394
|
+
this.audit("ip.locked", { ip: remoteIP });
|
|
94395
|
+
try {
|
|
94396
|
+
sendWithBackpressure({
|
|
94397
|
+
command: "login",
|
|
94398
|
+
message: "LOCKED_IP",
|
|
94399
|
+
data: null
|
|
94400
|
+
});
|
|
94401
|
+
} catch (e) {}
|
|
94402
|
+
break;
|
|
94403
|
+
}
|
|
94400
94404
|
}
|
|
94401
94405
|
const now = Date.now();
|
|
94402
94406
|
const record = this.loginAttemptCache.get(username) || {
|
|
@@ -95211,9 +95215,8 @@ export const ${databaseName} = {};
|
|
|
95211
95215
|
if (this.users && Array.isArray(this.users) && this.users.length > 0) {
|
|
95212
95216
|
const u = this.users[0] || { username: "" };
|
|
95213
95217
|
const uname = u.username || null;
|
|
95214
|
-
|
|
95215
|
-
|
|
95216
|
-
cred = encodeURIComponent(String(uname)) + ":" + encodeURIComponent(String(pwd)) + "@";
|
|
95218
|
+
if (uname && typeof u.password === "string" && u.password) {
|
|
95219
|
+
cred = encodeURIComponent(String(uname)) + ":" + "*****" + "@";
|
|
95217
95220
|
} else if (uname) {
|
|
95218
95221
|
cred = encodeURIComponent(String(uname)) + "@";
|
|
95219
95222
|
}
|
|
@@ -95254,7 +95257,7 @@ import { spawn } from "node:child_process";
|
|
|
95254
95257
|
import Storage from "@scriptdb/storage";
|
|
95255
95258
|
var pkgData = `{
|
|
95256
95259
|
"name": "scriptdb-workspace",
|
|
95257
|
-
"version": "1.0
|
|
95260
|
+
"version": "1.1.0",
|
|
95258
95261
|
"description": "ScriptDB workspace for custom scripts, services, and databases",
|
|
95259
95262
|
"private": true,
|
|
95260
95263
|
"devDependencies": {
|
|
@@ -95341,6 +95344,10 @@ var configDefault = {
|
|
|
95341
95344
|
GITHUB_URL: "",
|
|
95342
95345
|
GITHUB_TOKEN: "",
|
|
95343
95346
|
GITHUB_BRANCH: "main",
|
|
95347
|
+
enableIpLockout: true,
|
|
95348
|
+
ipFailWindowMs: 900000,
|
|
95349
|
+
maxLoginAttempts: 5,
|
|
95350
|
+
lockDurationMs: 900000,
|
|
95344
95351
|
users: [
|
|
95345
95352
|
{
|
|
95346
95353
|
username: "admin",
|
|
@@ -97296,7 +97303,7 @@ function ensureScriptDBDir() {
|
|
|
97296
97303
|
if (!existsSync3(PACKAGE_JSON)) {
|
|
97297
97304
|
const packageJsonContent = {
|
|
97298
97305
|
name: "scriptdb-workspace",
|
|
97299
|
-
version: "1.0
|
|
97306
|
+
version: "1.1.0",
|
|
97300
97307
|
description: "ScriptDB workspace for custom scripts, services, and databases",
|
|
97301
97308
|
private: true,
|
|
97302
97309
|
devDependencies: {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { parentPort } = require('worker_threads');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
|
|
4
|
+
parentPort.on('message', async (msg) => {
|
|
5
|
+
const { id, password, hash } = msg;
|
|
6
|
+
try {
|
|
7
|
+
const ok = await bcrypt.compare(password, hash);
|
|
8
|
+
parentPort.postMessage({ id, ok });
|
|
9
|
+
} catch (e) {
|
|
10
|
+
parentPort.postMessage({ id, error: (e && e.message) || String(e) });
|
|
11
|
+
}
|
|
12
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const { parentPort } = require('worker_threads');
|
|
2
|
+
|
|
3
|
+
// Lightweight validation + sanitization worker
|
|
4
|
+
// Expects messages: { id, action: 'validate', payload, options }
|
|
5
|
+
|
|
6
|
+
function shallowValidate(payload, schema, maxDepth = 4, maxNodes = 1000) {
|
|
7
|
+
const out = {};
|
|
8
|
+
let nodes = 0;
|
|
9
|
+
const checkDepth = (obj, depth) => {
|
|
10
|
+
if (depth > maxDepth) return false;
|
|
11
|
+
if (nodes++ > maxNodes) return false;
|
|
12
|
+
if (obj && typeof obj === 'object') {
|
|
13
|
+
for (const k of Object.keys(obj)) {
|
|
14
|
+
const expected = (schema || {})[k];
|
|
15
|
+
if (!expected) continue;
|
|
16
|
+
const val = obj[k];
|
|
17
|
+
if (expected === 'string' && typeof val === 'string') out[k] = val;
|
|
18
|
+
else if (expected === 'number' && typeof val === 'number') out[k] = val;
|
|
19
|
+
else if (expected === 'object' && val && typeof val === 'object') {
|
|
20
|
+
if (depth + 1 <= maxDepth) {
|
|
21
|
+
out[k] = {};
|
|
22
|
+
for (const nk of Object.keys(val)) {
|
|
23
|
+
if ((schema || {})[nk]) out[k][nk] = val[nk];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
};
|
|
32
|
+
if (!checkDepth(payload, 0)) return {};
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sanitizeResult(res, opts) {
|
|
37
|
+
const allowedKeys = opts && opts.allowedKeys ? opts.allowedKeys : ['result', 'value', 'items', 'length', 'status', 'message'];
|
|
38
|
+
const maxNodes = opts && opts.maxNodes || 2000;
|
|
39
|
+
const maxDepth = opts && opts.maxDepth || 6;
|
|
40
|
+
const maxString = opts && opts.maxString || 1000;
|
|
41
|
+
let nodes = 0;
|
|
42
|
+
const rec = (input, depth = 0) => {
|
|
43
|
+
if (nodes++ > maxNodes) return '[REDACTED_NODES]';
|
|
44
|
+
if (depth > maxDepth) return '[REDACTED_DEPTH]';
|
|
45
|
+
if (input === null || input === undefined) return input;
|
|
46
|
+
if (typeof input === 'string') {
|
|
47
|
+
if (input.length > maxString) return input.slice(0, maxString) + '...[TRUNC]';
|
|
48
|
+
return input;
|
|
49
|
+
}
|
|
50
|
+
if (typeof input === 'number' || typeof input === 'boolean') return input;
|
|
51
|
+
if (Array.isArray(input)) return input.map((v) => rec(v, depth + 1));
|
|
52
|
+
if (typeof input === 'object') {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const k of Object.keys(input)) {
|
|
55
|
+
if (allowedKeys.includes(k)) out[k] = rec(input[k], depth + 1);
|
|
56
|
+
else out[k] = '[REDACTED]';
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
return '[REDACTED]';
|
|
61
|
+
};
|
|
62
|
+
return rec(res, 0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
parentPort.on('message', async (m) => {
|
|
66
|
+
if (!m || !m.id) return;
|
|
67
|
+
try {
|
|
68
|
+
if (m.action === 'validate') {
|
|
69
|
+
const allowed = shallowValidate(m.payload, m.options.schema, m.options.maxDepth, m.options.maxNodes);
|
|
70
|
+
parentPort.postMessage({ id: m.id, res: allowed });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (m.action === 'sanitize') {
|
|
74
|
+
const out = sanitizeResult(m.payload, m.options || {});
|
|
75
|
+
parentPort.postMessage({ id: m.id, res: out });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
parentPort.postMessage({ id: m.id, error: 'unknown action' });
|
|
79
|
+
} catch (e) {
|
|
80
|
+
parentPort.postMessage({ id: m.id, error: (e && e.message) || String(e) });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { parentPort } = require('worker_threads');
|
|
2
|
+
const VMModule = require('@scriptdb/vm');
|
|
3
|
+
const VM = VMModule && VMModule.VM ? VMModule.VM : VMModule;
|
|
4
|
+
|
|
5
|
+
let vmInstance = null;
|
|
6
|
+
|
|
7
|
+
parentPort.on('message', async (msg) => {
|
|
8
|
+
const { id, action, options, payload } = msg;
|
|
9
|
+
try {
|
|
10
|
+
if (action === 'init') {
|
|
11
|
+
vmInstance = new VM(options || {});
|
|
12
|
+
parentPort.postMessage({ id, ok: true });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (action === 'run') {
|
|
16
|
+
if (!vmInstance) throw new Error('vm not initialized');
|
|
17
|
+
const res = await vmInstance.run(payload);
|
|
18
|
+
parentPort.postMessage({ id, res });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
parentPort.postMessage({ id, error: 'unknown action' });
|
|
22
|
+
} catch (e) {
|
|
23
|
+
parentPort.postMessage({ id, error: (e && e.message) || String(e) });
|
|
24
|
+
}
|
|
25
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scriptdb/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI tool to start and manage ScriptDB server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"dev": "bun --watch src/index.ts",
|
|
16
|
-
"build": "bun build src/index.ts --outdir dist --target node --format esm --external '@scriptdb/*'",
|
|
16
|
+
"build": "bun build src/index.ts --outdir dist --target node --format esm --external '@scriptdb/*' && node scripts/copy-workers.cjs",
|
|
17
17
|
"build:types": "tsc --emitDeclarationOnly",
|
|
18
18
|
"build:all": "bun run build && bun run build:types",
|
|
19
19
|
"test": "bun test",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"typescript": "^5.0.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@scriptdb/client": "^1.0
|
|
42
|
-
"@scriptdb/server": "^1.0
|
|
41
|
+
"@scriptdb/client": "^1.1.0",
|
|
42
|
+
"@scriptdb/server": "^1.1.0",
|
|
43
43
|
"bcryptjs": "^3.0.3",
|
|
44
44
|
"bottleneck": "^2.19.5",
|
|
45
45
|
"jsonwebtoken": "^9.0.3",
|