lsh-framework 3.0.0 ā 3.1.1
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/dist/cli.js +1 -1
- package/dist/commands/doctor.js +32 -15
- package/dist/commands/init.js +31 -12
- package/dist/commands/self.js +0 -1
- package/dist/constants/validation.js +2 -0
- package/dist/daemon/lshd.js +21 -4
- package/dist/daemon/saas-api-routes.js +44 -37
- package/dist/daemon/saas-api-server.js +9 -5
- package/dist/lib/cron-job-manager.js +2 -0
- package/dist/lib/ipfs-secrets-storage.js +26 -7
- package/dist/lib/job-manager.js +0 -1
- package/dist/lib/lshrc-init.js +0 -1
- package/dist/lib/saas-audit.js +6 -3
- package/dist/lib/saas-auth.js +6 -3
- package/dist/lib/saas-billing.js +10 -2
- package/dist/lib/saas-encryption.js +2 -1
- package/dist/lib/saas-organizations.js +5 -0
- package/dist/lib/saas-secrets.js +4 -1
- package/dist/lib/saas-types.js +57 -0
- package/dist/lib/secrets-manager.js +63 -6
- package/dist/lib/supabase-client.js +1 -2
- package/dist/services/secrets/secrets.js +63 -24
- package/package.json +4 -3
|
@@ -160,6 +160,8 @@ export class CronJobManager extends BaseJobManager {
|
|
|
160
160
|
*/
|
|
161
161
|
async getJobReport(jobId) {
|
|
162
162
|
// Try to get historical data from database if available, otherwise use current job info
|
|
163
|
+
// Using any[] because getJobHistory returns ShellJob (snake_case properties)
|
|
164
|
+
// but getJob returns JobSpec (camelCase properties), and this code handles both
|
|
163
165
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
166
|
let jobs = [];
|
|
165
167
|
try {
|
|
@@ -269,13 +269,32 @@ export class IPFSSecretsStorage {
|
|
|
269
269
|
* Decrypt secrets using AES-256
|
|
270
270
|
*/
|
|
271
271
|
decryptSecrets(encryptedData, encryptionKey) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
try {
|
|
273
|
+
const [ivHex, encrypted] = encryptedData.split(':');
|
|
274
|
+
const key = crypto.createHash('sha256').update(encryptionKey).digest();
|
|
275
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
276
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
277
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
278
|
+
decrypted += decipher.final('utf8');
|
|
279
|
+
return JSON.parse(decrypted);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const err = error;
|
|
283
|
+
// Catch crypto errors (bad decrypt, wrong block length) AND JSON parse errors
|
|
284
|
+
// (wrong key can produce garbage that fails JSON.parse)
|
|
285
|
+
if (err.message.includes('bad decrypt') ||
|
|
286
|
+
err.message.includes('wrong final block length') ||
|
|
287
|
+
err.message.includes('Unexpected token') ||
|
|
288
|
+
err.message.includes('JSON')) {
|
|
289
|
+
throw new Error('Decryption failed. This usually means:\n' +
|
|
290
|
+
' 1. You need to set LSH_SECRETS_KEY environment variable\n' +
|
|
291
|
+
' 2. The key must match the one used during encryption\n' +
|
|
292
|
+
' 3. Generate a shared key with: lsh secrets key\n' +
|
|
293
|
+
' 4. Add it to your .env: LSH_SECRETS_KEY=<key>\n' +
|
|
294
|
+
'\nOriginal error: ' + err.message);
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
279
298
|
}
|
|
280
299
|
/**
|
|
281
300
|
* Generate IPFS-compatible CID from content
|
package/dist/lib/job-manager.js
CHANGED
package/dist/lib/lshrc-init.js
CHANGED
package/dist/lib/saas-audit.js
CHANGED
|
@@ -173,6 +173,7 @@ export class AuditLogger {
|
|
|
173
173
|
/**
|
|
174
174
|
* Map database log to AuditLog type
|
|
175
175
|
*/
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
176
177
|
mapDbLogToLog(dbLog) {
|
|
177
178
|
return {
|
|
178
179
|
id: dbLog.id,
|
|
@@ -200,9 +201,11 @@ export const auditLogger = new AuditLogger();
|
|
|
200
201
|
* Helper function to extract IP from request
|
|
201
202
|
*/
|
|
202
203
|
export function getIpFromRequest(req) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
205
|
+
const forwardedIp = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : undefined;
|
|
206
|
+
const realIp = req.headers['x-real-ip'];
|
|
207
|
+
return (forwardedIp ||
|
|
208
|
+
(typeof realIp === 'string' ? realIp : undefined) ||
|
|
206
209
|
req.socket?.remoteAddress);
|
|
207
210
|
}
|
|
208
211
|
/**
|
package/dist/lib/saas-auth.js
CHANGED
|
@@ -82,7 +82,7 @@ export function verifyToken(token) {
|
|
|
82
82
|
type: decoded.type,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
catch (
|
|
85
|
+
catch (_error) {
|
|
86
86
|
throw new Error('Invalid or expired token');
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -320,6 +320,7 @@ export class AuthService {
|
|
|
320
320
|
if (error) {
|
|
321
321
|
throw new Error(`Failed to get user organizations: ${error.message}`);
|
|
322
322
|
}
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB join result
|
|
323
324
|
return (data || []).map((row) => this.mapDbOrgToOrg(row.organizations));
|
|
324
325
|
}
|
|
325
326
|
/**
|
|
@@ -337,7 +338,7 @@ export class AuthService {
|
|
|
337
338
|
return generateToken();
|
|
338
339
|
}
|
|
339
340
|
const resetToken = generateToken();
|
|
340
|
-
const
|
|
341
|
+
const _expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour (for future use)
|
|
341
342
|
// Store reset token (we'll need a password_reset_tokens table)
|
|
342
343
|
// For now, just return the token
|
|
343
344
|
return resetToken;
|
|
@@ -345,7 +346,7 @@ export class AuthService {
|
|
|
345
346
|
/**
|
|
346
347
|
* Reset password
|
|
347
348
|
*/
|
|
348
|
-
async resetPassword(
|
|
349
|
+
async resetPassword(_token, _newPassword) {
|
|
349
350
|
// TODO: Implement password reset
|
|
350
351
|
// Need to create password_reset_tokens table
|
|
351
352
|
throw new Error('Not implemented');
|
|
@@ -378,6 +379,7 @@ export class AuthService {
|
|
|
378
379
|
/**
|
|
379
380
|
* Map database user to User type
|
|
380
381
|
*/
|
|
382
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
381
383
|
mapDbUserToUser(dbUser) {
|
|
382
384
|
return {
|
|
383
385
|
id: dbUser.id,
|
|
@@ -403,6 +405,7 @@ export class AuthService {
|
|
|
403
405
|
/**
|
|
404
406
|
* Map database organization to Organization type
|
|
405
407
|
*/
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
406
409
|
mapDbOrgToOrg(dbOrg) {
|
|
407
410
|
return {
|
|
408
411
|
id: dbOrg.id,
|
package/dist/lib/saas-billing.js
CHANGED
|
@@ -155,19 +155,21 @@ export class BillingService {
|
|
|
155
155
|
/**
|
|
156
156
|
* Verify webhook signature
|
|
157
157
|
*/
|
|
158
|
-
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe event structure
|
|
159
|
+
verifyWebhookSignature(payload, _signature) {
|
|
159
160
|
// In production, use Stripe's webhook signature verification
|
|
160
161
|
// For now, just parse the payload
|
|
161
162
|
try {
|
|
162
163
|
return JSON.parse(payload);
|
|
163
164
|
}
|
|
164
|
-
catch (
|
|
165
|
+
catch (_error) {
|
|
165
166
|
throw new Error('Invalid webhook payload');
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
/**
|
|
169
170
|
* Handle checkout completed
|
|
170
171
|
*/
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe checkout session object
|
|
171
173
|
async handleCheckoutCompleted(session) {
|
|
172
174
|
const organizationId = session.metadata?.organization_id;
|
|
173
175
|
if (!organizationId) {
|
|
@@ -180,6 +182,7 @@ export class BillingService {
|
|
|
180
182
|
/**
|
|
181
183
|
* Handle subscription updated
|
|
182
184
|
*/
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe subscription object
|
|
183
186
|
async handleSubscriptionUpdated(subscription) {
|
|
184
187
|
const organizationId = subscription.metadata?.organization_id;
|
|
185
188
|
if (!organizationId) {
|
|
@@ -233,6 +236,7 @@ export class BillingService {
|
|
|
233
236
|
/**
|
|
234
237
|
* Handle subscription deleted
|
|
235
238
|
*/
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe subscription object
|
|
236
240
|
async handleSubscriptionDeleted(subscription) {
|
|
237
241
|
const organizationId = subscription.metadata?.organization_id;
|
|
238
242
|
if (!organizationId) {
|
|
@@ -265,6 +269,7 @@ export class BillingService {
|
|
|
265
269
|
/**
|
|
266
270
|
* Handle invoice paid
|
|
267
271
|
*/
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe invoice object
|
|
268
273
|
async handleInvoicePaid(invoice) {
|
|
269
274
|
const organizationId = invoice.subscription_metadata?.organization_id;
|
|
270
275
|
if (!organizationId) {
|
|
@@ -287,6 +292,7 @@ export class BillingService {
|
|
|
287
292
|
/**
|
|
288
293
|
* Handle invoice payment failed
|
|
289
294
|
*/
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe invoice object
|
|
290
296
|
async handleInvoicePaymentFailed(invoice) {
|
|
291
297
|
const organizationId = invoice.subscription_metadata?.organization_id;
|
|
292
298
|
if (!organizationId) {
|
|
@@ -353,6 +359,7 @@ export class BillingService {
|
|
|
353
359
|
/**
|
|
354
360
|
* Map database subscription to Subscription type
|
|
355
361
|
*/
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
356
363
|
mapDbSubscriptionToSubscription(dbSub) {
|
|
357
364
|
return {
|
|
358
365
|
id: dbSub.id,
|
|
@@ -377,6 +384,7 @@ export class BillingService {
|
|
|
377
384
|
/**
|
|
378
385
|
* Map database invoice to Invoice type
|
|
379
386
|
*/
|
|
387
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
380
388
|
mapDbInvoiceToInvoice(dbInvoice) {
|
|
381
389
|
return {
|
|
382
390
|
id: dbInvoice.id,
|
|
@@ -7,7 +7,7 @@ import { getSupabaseClient } from './supabase-client.js';
|
|
|
7
7
|
const ALGORITHM = 'aes-256-cbc';
|
|
8
8
|
const KEY_LENGTH = 32; // 256 bits
|
|
9
9
|
const IV_LENGTH = 16; // 128 bits
|
|
10
|
-
const
|
|
10
|
+
const _SALT_LENGTH = 32; // Reserved for future use
|
|
11
11
|
const PBKDF2_ITERATIONS = 100000;
|
|
12
12
|
/**
|
|
13
13
|
* Get master encryption key from environment
|
|
@@ -199,6 +199,7 @@ export class EncryptionService {
|
|
|
199
199
|
/**
|
|
200
200
|
* Map database key to EncryptionKey type
|
|
201
201
|
*/
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
202
203
|
mapDbKeyToKey(dbKey) {
|
|
203
204
|
return {
|
|
204
205
|
id: dbKey.id,
|
|
@@ -332,6 +332,7 @@ export class OrganizationService {
|
|
|
332
332
|
/**
|
|
333
333
|
* Map database org to Organization type
|
|
334
334
|
*/
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
335
336
|
mapDbOrgToOrg(dbOrg) {
|
|
336
337
|
return {
|
|
337
338
|
id: dbOrg.id,
|
|
@@ -352,6 +353,7 @@ export class OrganizationService {
|
|
|
352
353
|
/**
|
|
353
354
|
* Map database member to OrganizationMember type
|
|
354
355
|
*/
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
355
357
|
mapDbMemberToMember(dbMember) {
|
|
356
358
|
return {
|
|
357
359
|
id: dbMember.id,
|
|
@@ -368,6 +370,7 @@ export class OrganizationService {
|
|
|
368
370
|
/**
|
|
369
371
|
* Map database member detailed to OrganizationMemberDetailed type
|
|
370
372
|
*/
|
|
373
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row with joined user data
|
|
371
374
|
mapDbMemberDetailedToMemberDetailed(dbMember) {
|
|
372
375
|
return {
|
|
373
376
|
id: dbMember.id,
|
|
@@ -558,6 +561,7 @@ export class TeamService {
|
|
|
558
561
|
/**
|
|
559
562
|
* Map database team to Team type
|
|
560
563
|
*/
|
|
564
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
561
565
|
mapDbTeamToTeam(dbTeam) {
|
|
562
566
|
return {
|
|
563
567
|
id: dbTeam.id,
|
|
@@ -574,6 +578,7 @@ export class TeamService {
|
|
|
574
578
|
/**
|
|
575
579
|
* Map database team member to TeamMember type
|
|
576
580
|
*/
|
|
581
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
577
582
|
mapDbTeamMemberToTeamMember(dbMember) {
|
|
578
583
|
return {
|
|
579
584
|
id: dbMember.id,
|
package/dist/lib/saas-secrets.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* LSH SaaS Secrets Management Service
|
|
3
3
|
* Multi-tenant secrets with per-team encryption
|
|
4
4
|
*/
|
|
5
|
+
import { getErrorMessage, } from './saas-types.js';
|
|
5
6
|
import { getSupabaseClient } from './supabase-client.js';
|
|
6
7
|
import { encryptionService } from './saas-encryption.js';
|
|
7
8
|
import { auditLogger } from './saas-audit.js';
|
|
@@ -216,6 +217,7 @@ export class SecretsService {
|
|
|
216
217
|
if (error) {
|
|
217
218
|
throw new Error(`Failed to get secrets summary: ${error.message}`);
|
|
218
219
|
}
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row from view
|
|
219
221
|
return (data || []).map((row) => ({
|
|
220
222
|
teamId: row.team_id,
|
|
221
223
|
teamName: row.team_name,
|
|
@@ -313,7 +315,7 @@ export class SecretsService {
|
|
|
313
315
|
}
|
|
314
316
|
}
|
|
315
317
|
catch (error) {
|
|
316
|
-
errors.push(`${secret.key}: ${error
|
|
318
|
+
errors.push(`${secret.key}: ${getErrorMessage(error)}`);
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
return { created, updated, errors };
|
|
@@ -351,6 +353,7 @@ export class SecretsService {
|
|
|
351
353
|
/**
|
|
352
354
|
* Map database secret to Secret type
|
|
353
355
|
*/
|
|
356
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- DB row type varies by schema
|
|
354
357
|
mapDbSecretToSecret(dbSecret) {
|
|
355
358
|
return {
|
|
356
359
|
id: dbSecret.id,
|
package/dist/lib/saas-types.js
CHANGED
|
@@ -106,3 +106,60 @@ export var ErrorCode;
|
|
|
106
106
|
ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
107
107
|
ErrorCode["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
108
108
|
})(ErrorCode || (ErrorCode = {}));
|
|
109
|
+
/**
|
|
110
|
+
* Helper to safely extract error message
|
|
111
|
+
*/
|
|
112
|
+
export function getErrorMessage(error) {
|
|
113
|
+
if (error instanceof Error) {
|
|
114
|
+
return error.message;
|
|
115
|
+
}
|
|
116
|
+
if (typeof error === 'string') {
|
|
117
|
+
return error;
|
|
118
|
+
}
|
|
119
|
+
return 'Unknown error occurred';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Helper to safely extract error for logging
|
|
123
|
+
*/
|
|
124
|
+
export function getErrorDetails(error) {
|
|
125
|
+
if (error instanceof Error) {
|
|
126
|
+
return {
|
|
127
|
+
message: error.message,
|
|
128
|
+
stack: error.stack,
|
|
129
|
+
code: error.code,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { message: String(error) };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Helper to get authenticated user from request.
|
|
136
|
+
* Use after authenticateUser middleware - throws if user not present.
|
|
137
|
+
*/
|
|
138
|
+
export function getAuthenticatedUser(req) {
|
|
139
|
+
if (!req.user) {
|
|
140
|
+
throw new Error('User not authenticated');
|
|
141
|
+
}
|
|
142
|
+
return req.user;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Create a standardized API error response
|
|
146
|
+
*/
|
|
147
|
+
export function createErrorResponse(code, message, details) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: {
|
|
151
|
+
code,
|
|
152
|
+
message,
|
|
153
|
+
details,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create a standardized API success response
|
|
159
|
+
*/
|
|
160
|
+
export function createSuccessResponse(data) {
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
data,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -14,15 +14,53 @@ export class SecretsManager {
|
|
|
14
14
|
storage;
|
|
15
15
|
encryptionKey;
|
|
16
16
|
gitInfo;
|
|
17
|
-
|
|
17
|
+
globalMode;
|
|
18
|
+
homeDir;
|
|
19
|
+
constructor(userIdOrOptions, encryptionKey, detectGit) {
|
|
18
20
|
this.storage = new IPFSSecretsStorage();
|
|
21
|
+
this.homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
22
|
+
// Handle both legacy and new constructor signatures
|
|
23
|
+
let options;
|
|
24
|
+
if (typeof userIdOrOptions === 'object') {
|
|
25
|
+
options = userIdOrOptions;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
options = {
|
|
29
|
+
userId: userIdOrOptions,
|
|
30
|
+
encryptionKey,
|
|
31
|
+
detectGit: detectGit ?? true,
|
|
32
|
+
globalMode: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
this.globalMode = options.globalMode ?? false;
|
|
19
36
|
// Use provided key or generate from machine ID + user
|
|
20
|
-
this.encryptionKey = encryptionKey || this.getDefaultEncryptionKey();
|
|
21
|
-
// Auto-detect git repo context
|
|
22
|
-
if (detectGit) {
|
|
37
|
+
this.encryptionKey = options.encryptionKey || this.getDefaultEncryptionKey();
|
|
38
|
+
// Auto-detect git repo context (skip if in global mode)
|
|
39
|
+
if (!this.globalMode && (options.detectGit ?? true)) {
|
|
23
40
|
this.gitInfo = getGitRepoInfo();
|
|
24
41
|
}
|
|
25
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if running in global mode
|
|
45
|
+
*/
|
|
46
|
+
isGlobalMode() {
|
|
47
|
+
return this.globalMode;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the home directory path
|
|
51
|
+
*/
|
|
52
|
+
getHomeDir() {
|
|
53
|
+
return this.homeDir;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve file path - in global mode, resolves relative to $HOME
|
|
57
|
+
*/
|
|
58
|
+
resolveFilePath(filePath) {
|
|
59
|
+
if (this.globalMode && !path.isAbsolute(filePath)) {
|
|
60
|
+
return path.join(this.homeDir, filePath);
|
|
61
|
+
}
|
|
62
|
+
return filePath;
|
|
63
|
+
}
|
|
26
64
|
/**
|
|
27
65
|
* Cleanup resources (stop timers, close connections)
|
|
28
66
|
* Call this when done to allow process to exit
|
|
@@ -430,8 +468,13 @@ export class SecretsManager {
|
|
|
430
468
|
/**
|
|
431
469
|
* Get the default environment name based on context
|
|
432
470
|
* v2.0: In git repo, default is repo name; otherwise 'dev'
|
|
471
|
+
* Global mode: always returns 'dev' (which resolves to 'global' namespace)
|
|
433
472
|
*/
|
|
434
473
|
getDefaultEnvironment() {
|
|
474
|
+
// Global mode uses simple 'dev' which maps to 'global' namespace
|
|
475
|
+
if (this.globalMode) {
|
|
476
|
+
return 'dev';
|
|
477
|
+
}
|
|
435
478
|
// Check for v1 compatibility mode
|
|
436
479
|
if (process.env.LSH_V1_COMPAT === 'true') {
|
|
437
480
|
return 'dev'; // v1.x behavior
|
|
@@ -447,11 +490,19 @@ export class SecretsManager {
|
|
|
447
490
|
* v2.0: Returns environment name with repo context if in a git repo
|
|
448
491
|
*
|
|
449
492
|
* Behavior:
|
|
493
|
+
* - Global mode: returns 'global' or 'global_env' (e.g., global_staging)
|
|
450
494
|
* - Empty env in repo: returns just repo name (v2.0 default)
|
|
451
495
|
* - Named env in repo: returns repo_env (e.g., repo_staging)
|
|
452
496
|
* - Any env outside repo: returns env as-is
|
|
453
497
|
*/
|
|
454
498
|
getRepoAwareEnvironment(environment) {
|
|
499
|
+
// Global mode uses 'global' namespace
|
|
500
|
+
if (this.globalMode) {
|
|
501
|
+
if (environment === '' || environment === 'default' || environment === 'dev') {
|
|
502
|
+
return 'global';
|
|
503
|
+
}
|
|
504
|
+
return `global_${environment}`;
|
|
505
|
+
}
|
|
455
506
|
if (this.gitInfo?.repoName) {
|
|
456
507
|
// v2.0: Empty environment means "use repo name only"
|
|
457
508
|
if (environment === '' || environment === 'default') {
|
|
@@ -610,8 +661,14 @@ LSH_SECRETS_KEY=${this.encryptionKey}
|
|
|
610
661
|
// In load mode, suppress all output except the final export commands
|
|
611
662
|
const out = loadMode ? () => { } : console.log;
|
|
612
663
|
out(`\nš Smart sync for: ${displayEnv}\n`);
|
|
613
|
-
// Show
|
|
614
|
-
if (this.
|
|
664
|
+
// Show workspace context
|
|
665
|
+
if (this.globalMode) {
|
|
666
|
+
out('š Global Workspace:');
|
|
667
|
+
out(` Location: ${this.homeDir}`);
|
|
668
|
+
out(` Namespace: global`);
|
|
669
|
+
out();
|
|
670
|
+
}
|
|
671
|
+
else if (this.gitInfo?.isGitRepo) {
|
|
615
672
|
out('š Git Repository:');
|
|
616
673
|
out(` Repo: ${this.gitInfo.repoName || 'unknown'}`);
|
|
617
674
|
if (this.gitInfo.currentBranch) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createClient } from '@supabase/supabase-js';
|
|
6
6
|
export class SupabaseClient {
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
7
|
client;
|
|
9
8
|
config;
|
|
10
9
|
constructor(config) {
|
|
@@ -32,7 +31,7 @@ export class SupabaseClient {
|
|
|
32
31
|
*/
|
|
33
32
|
async testConnection() {
|
|
34
33
|
try {
|
|
35
|
-
const {
|
|
34
|
+
const { error } = await this.client
|
|
36
35
|
.from('shell_history')
|
|
37
36
|
.select('count')
|
|
38
37
|
.limit(1);
|