lsh-framework 1.3.2 → 1.4.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/.env.example +30 -1
- package/README.md +25 -4
- package/dist/cli.js +6 -0
- package/dist/commands/config.js +240 -0
- package/dist/daemon/saas-api-routes.js +778 -0
- package/dist/daemon/saas-api-server.js +225 -0
- package/dist/lib/config-manager.js +317 -0
- package/dist/lib/database-persistence.js +75 -3
- package/dist/lib/env-validator.js +17 -0
- package/dist/lib/local-storage-adapter.js +493 -0
- package/dist/lib/saas-audit.js +213 -0
- package/dist/lib/saas-auth.js +427 -0
- package/dist/lib/saas-billing.js +402 -0
- package/dist/lib/saas-email.js +402 -0
- package/dist/lib/saas-encryption.js +220 -0
- package/dist/lib/saas-organizations.js +592 -0
- package/dist/lib/saas-secrets.js +378 -0
- package/dist/lib/saas-types.js +108 -0
- package/dist/lib/supabase-client.js +77 -11
- package/package.json +13 -2
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Database Persistence Layer for LSH
|
|
3
|
-
* Handles data synchronization with Supabase PostgreSQL
|
|
3
|
+
* Handles data synchronization with Supabase PostgreSQL or local storage fallback
|
|
4
4
|
*/
|
|
5
|
-
import { supabaseClient } from './supabase-client.js';
|
|
5
|
+
import { supabaseClient, isSupabaseConfigured } from './supabase-client.js';
|
|
6
6
|
import * as os from 'os';
|
|
7
|
+
import { LocalStorageAdapter } from './local-storage-adapter.js';
|
|
7
8
|
export class DatabasePersistence {
|
|
8
9
|
client;
|
|
10
|
+
localStorage;
|
|
9
11
|
userId;
|
|
10
12
|
sessionId;
|
|
13
|
+
useLocalStorage;
|
|
11
14
|
constructor(userId) {
|
|
12
|
-
this.
|
|
15
|
+
this.useLocalStorage = !isSupabaseConfigured();
|
|
16
|
+
if (this.useLocalStorage) {
|
|
17
|
+
console.log('⚠️ Supabase not configured - using local storage fallback');
|
|
18
|
+
this.localStorage = new LocalStorageAdapter(userId);
|
|
19
|
+
this.localStorage.initialize().catch(err => {
|
|
20
|
+
console.error('Failed to initialize local storage:', err);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Supabase is configured, use it exclusively
|
|
25
|
+
try {
|
|
26
|
+
this.client = supabaseClient.getClient();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// If Supabase is configured but fails, throw error instead of falling back
|
|
30
|
+
console.error('⚠️ Supabase is configured but connection failed:', error);
|
|
31
|
+
throw new Error('Supabase connection failed. Check your SUPABASE_URL and SUPABASE_ANON_KEY configuration.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
13
34
|
this.userId = userId ? this.generateUserUUID(userId) : undefined;
|
|
14
35
|
this.sessionId = this.generateSessionId();
|
|
15
36
|
}
|
|
@@ -51,6 +72,9 @@ export class DatabasePersistence {
|
|
|
51
72
|
* Save shell history entry
|
|
52
73
|
*/
|
|
53
74
|
async saveHistoryEntry(entry) {
|
|
75
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
76
|
+
return this.localStorage.saveHistoryEntry(entry);
|
|
77
|
+
}
|
|
54
78
|
try {
|
|
55
79
|
const insertData = {
|
|
56
80
|
...entry,
|
|
@@ -80,6 +104,9 @@ export class DatabasePersistence {
|
|
|
80
104
|
* Get shell history entries
|
|
81
105
|
*/
|
|
82
106
|
async getHistoryEntries(limit = 100, offset = 0) {
|
|
107
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
108
|
+
return this.localStorage.getHistoryEntries(limit, offset);
|
|
109
|
+
}
|
|
83
110
|
try {
|
|
84
111
|
let query = this.client
|
|
85
112
|
.from('shell_history')
|
|
@@ -109,6 +136,9 @@ export class DatabasePersistence {
|
|
|
109
136
|
* Save shell job
|
|
110
137
|
*/
|
|
111
138
|
async saveJob(job) {
|
|
139
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
140
|
+
return this.localStorage.saveJob(job);
|
|
141
|
+
}
|
|
112
142
|
try {
|
|
113
143
|
const insertData = {
|
|
114
144
|
...job,
|
|
@@ -138,6 +168,9 @@ export class DatabasePersistence {
|
|
|
138
168
|
* Update shell job status
|
|
139
169
|
*/
|
|
140
170
|
async updateJobStatus(jobId, status, exitCode) {
|
|
171
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
172
|
+
return this.localStorage.updateJobStatus(jobId, status, exitCode);
|
|
173
|
+
}
|
|
141
174
|
try {
|
|
142
175
|
const updateData = {
|
|
143
176
|
status,
|
|
@@ -176,6 +209,9 @@ export class DatabasePersistence {
|
|
|
176
209
|
* Get active jobs
|
|
177
210
|
*/
|
|
178
211
|
async getActiveJobs() {
|
|
212
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
213
|
+
return this.localStorage.getActiveJobs();
|
|
214
|
+
}
|
|
179
215
|
try {
|
|
180
216
|
let query = this.client
|
|
181
217
|
.from('shell_jobs')
|
|
@@ -205,6 +241,9 @@ export class DatabasePersistence {
|
|
|
205
241
|
* Save shell configuration
|
|
206
242
|
*/
|
|
207
243
|
async saveConfiguration(config) {
|
|
244
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
245
|
+
return this.localStorage.saveConfiguration(config);
|
|
246
|
+
}
|
|
208
247
|
try {
|
|
209
248
|
const upsertData = {
|
|
210
249
|
...config,
|
|
@@ -235,6 +274,9 @@ export class DatabasePersistence {
|
|
|
235
274
|
* Get shell configuration
|
|
236
275
|
*/
|
|
237
276
|
async getConfiguration(key) {
|
|
277
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
278
|
+
return this.localStorage.getConfiguration(key);
|
|
279
|
+
}
|
|
238
280
|
try {
|
|
239
281
|
let query = this.client
|
|
240
282
|
.from('shell_configuration')
|
|
@@ -266,6 +308,9 @@ export class DatabasePersistence {
|
|
|
266
308
|
* Save shell alias
|
|
267
309
|
*/
|
|
268
310
|
async saveAlias(alias) {
|
|
311
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
312
|
+
return this.localStorage.saveAlias(alias);
|
|
313
|
+
}
|
|
269
314
|
try {
|
|
270
315
|
const upsertData = {
|
|
271
316
|
...alias,
|
|
@@ -296,6 +341,9 @@ export class DatabasePersistence {
|
|
|
296
341
|
* Get shell aliases
|
|
297
342
|
*/
|
|
298
343
|
async getAliases() {
|
|
344
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
345
|
+
return this.localStorage.getAliases();
|
|
346
|
+
}
|
|
299
347
|
try {
|
|
300
348
|
let query = this.client
|
|
301
349
|
.from('shell_aliases')
|
|
@@ -324,6 +372,9 @@ export class DatabasePersistence {
|
|
|
324
372
|
* Save shell function
|
|
325
373
|
*/
|
|
326
374
|
async saveFunction(func) {
|
|
375
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
376
|
+
return this.localStorage.saveFunction(func);
|
|
377
|
+
}
|
|
327
378
|
try {
|
|
328
379
|
const upsertData = {
|
|
329
380
|
...func,
|
|
@@ -354,6 +405,9 @@ export class DatabasePersistence {
|
|
|
354
405
|
* Get shell functions
|
|
355
406
|
*/
|
|
356
407
|
async getFunctions() {
|
|
408
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
409
|
+
return this.localStorage.getFunctions();
|
|
410
|
+
}
|
|
357
411
|
try {
|
|
358
412
|
let query = this.client
|
|
359
413
|
.from('shell_functions')
|
|
@@ -382,6 +436,9 @@ export class DatabasePersistence {
|
|
|
382
436
|
* Start a new shell session
|
|
383
437
|
*/
|
|
384
438
|
async startSession(workingDirectory, environmentVariables) {
|
|
439
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
440
|
+
return this.localStorage.startSession(workingDirectory, environmentVariables);
|
|
441
|
+
}
|
|
385
442
|
try {
|
|
386
443
|
const insertData = {
|
|
387
444
|
session_id: this.sessionId,
|
|
@@ -415,6 +472,9 @@ export class DatabasePersistence {
|
|
|
415
472
|
* End the current shell session
|
|
416
473
|
*/
|
|
417
474
|
async endSession() {
|
|
475
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
476
|
+
return this.localStorage.endSession();
|
|
477
|
+
}
|
|
418
478
|
try {
|
|
419
479
|
let query = this.client
|
|
420
480
|
.from('shell_sessions')
|
|
@@ -447,18 +507,27 @@ export class DatabasePersistence {
|
|
|
447
507
|
* Test database connectivity
|
|
448
508
|
*/
|
|
449
509
|
async testConnection() {
|
|
510
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
511
|
+
return this.localStorage.testConnection();
|
|
512
|
+
}
|
|
450
513
|
return await supabaseClient.testConnection();
|
|
451
514
|
}
|
|
452
515
|
/**
|
|
453
516
|
* Get session ID
|
|
454
517
|
*/
|
|
455
518
|
getSessionId() {
|
|
519
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
520
|
+
return this.localStorage.getSessionId();
|
|
521
|
+
}
|
|
456
522
|
return this.sessionId;
|
|
457
523
|
}
|
|
458
524
|
/**
|
|
459
525
|
* Get latest rows from all database tables
|
|
460
526
|
*/
|
|
461
527
|
async getLatestRows(limit = 5) {
|
|
528
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
529
|
+
return this.localStorage.getLatestRows(limit);
|
|
530
|
+
}
|
|
462
531
|
const result = {};
|
|
463
532
|
try {
|
|
464
533
|
// Get latest shell history entries
|
|
@@ -591,6 +660,9 @@ export class DatabasePersistence {
|
|
|
591
660
|
* Get latest rows from a specific table
|
|
592
661
|
*/
|
|
593
662
|
async getLatestRowsFromTable(tableName, limit = 5) {
|
|
663
|
+
if (this.useLocalStorage && this.localStorage) {
|
|
664
|
+
return this.localStorage.getLatestRowsFromTable(tableName, limit);
|
|
665
|
+
}
|
|
594
666
|
try {
|
|
595
667
|
const validTables = [
|
|
596
668
|
'shell_history',
|
|
@@ -203,6 +203,20 @@ export function validateEnvironment(requirements = LSH_ENV_REQUIREMENTS, env = p
|
|
|
203
203
|
result.recommendations.push(`${req.name} not set, using default: ${req.defaultValue}`);
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
|
+
// Storage mode detection and informational messaging
|
|
207
|
+
const hasSupabase = !!(env.SUPABASE_URL && env.SUPABASE_ANON_KEY);
|
|
208
|
+
const hasPostgres = !!env.DATABASE_URL;
|
|
209
|
+
if (!hasSupabase && !hasPostgres) {
|
|
210
|
+
result.recommendations.push('No database configured - using local file storage (~/.lsh/data/storage.json)');
|
|
211
|
+
result.recommendations.push('For team collaboration, configure SUPABASE_URL and SUPABASE_ANON_KEY');
|
|
212
|
+
result.recommendations.push('For local PostgreSQL, run: docker-compose up -d');
|
|
213
|
+
}
|
|
214
|
+
else if (hasPostgres && !hasSupabase) {
|
|
215
|
+
result.recommendations.push('Using local PostgreSQL database');
|
|
216
|
+
}
|
|
217
|
+
else if (hasSupabase) {
|
|
218
|
+
result.recommendations.push('Using Supabase cloud database - team sync enabled');
|
|
219
|
+
}
|
|
206
220
|
// Additional security checks
|
|
207
221
|
if (isProduction) {
|
|
208
222
|
if (env.LSH_ALLOW_DANGEROUS_COMMANDS === 'true') {
|
|
@@ -216,6 +230,9 @@ export function validateEnvironment(requirements = LSH_ENV_REQUIREMENTS, env = p
|
|
|
216
230
|
result.errors.push('LSH_JWT_SECRET must be at least 32 characters in production');
|
|
217
231
|
result.isValid = false;
|
|
218
232
|
}
|
|
233
|
+
if (!hasSupabase && !hasPostgres) {
|
|
234
|
+
result.warnings.push('Running in production without a database - local storage is not recommended for production');
|
|
235
|
+
}
|
|
219
236
|
}
|
|
220
237
|
return result;
|
|
221
238
|
}
|