@buildappolis/env-manager
Version:
Enterprise-grade environment variable management system with encryption, snapshots, and validation
353 lines (307 loc) • 9.25 kB
text/typescript
import type { DatabaseVariable } from '../types.js';
export interface EnvManagerConfig {
apiBasePath?: string;
projectRoot?: string;
}
export interface EnvManagerState {
authenticated: boolean;
variables: DatabaseVariable[];
snapshots: any[];
currentTab: string;
}
export interface ExportResult {
success: boolean;
message: string;
path?: string;
}
export class EnvManager {
private config: EnvManagerConfig;
private state: EnvManagerState;
constructor(config: EnvManagerConfig = {}) {
this.config = {
apiBasePath: config.apiBasePath || '/api',
projectRoot: config.projectRoot || process.cwd(),
};
this.state = {
authenticated: false,
variables: [],
snapshots: [],
currentTab: 'variables',
};
}
async init(): Promise<void> {
await this.checkAuthStatus();
if (this.state.authenticated) {
await this.loadData();
}
}
async checkAuthStatus(): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/auth`);
const data = await response.json();
this.state.authenticated = data.authenticated;
return this.state.authenticated;
} catch (error) {
console.error('Auth check failed:', error);
this.state.authenticated = false;
return false;
}
}
async authenticate(password: string): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/auth`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ password })
});
const data = await response.json();
if (data.success) {
this.state.authenticated = true;
await this.loadData();
return true;
}
return false;
} catch (error) {
console.error('Login failed:', error);
return false;
}
}
async loadData(): Promise<void> {
await this.loadVariables();
}
async loadVariables(): Promise<DatabaseVariable[]> {
try {
const response = await fetch(`${this.config.apiBasePath}/variables`);
const data = await response.json();
this.state.variables = data;
return this.state.variables;
} catch (error) {
console.error('Failed to load variables:', error);
return [];
}
}
async saveVariable(variable: Partial<DatabaseVariable>): Promise<boolean> {
try {
const isEdit = this.state.variables.some(v => v.name === variable.name);
const method = isEdit ? 'PUT' : 'POST';
const response = await fetch(`${this.config.apiBasePath}/variables`, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(variable)
});
if (response.ok) {
await this.loadVariables();
return true;
}
return false;
} catch (error) {
console.error('Failed to save variable:', error);
return false;
}
}
async deleteVariable(name: string): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/variables`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name })
});
if (response.ok) {
await this.loadVariables();
return true;
}
return false;
} catch (error) {
console.error('Failed to delete variable:', error);
return false;
}
}
async exportEnvExample(): Promise<ExportResult> {
try {
const response = await fetch(`${this.config.apiBasePath}/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: 'env-example' })
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const error = await response.json();
return { success: false, message: error.error };
}
} catch (error) {
console.error('Export failed:', error);
return { success: false, message: 'Failed to export .env.example' };
}
}
async exportEnv(password: string): Promise<ExportResult> {
try {
const response = await fetch(`${this.config.apiBasePath}/export`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'env',
password: password
})
});
if (response.ok) {
const result = await response.json();
return result;
} else {
const error = await response.json();
return { success: false, message: error.error };
}
} catch (error) {
console.error('Export failed:', error);
return { success: false, message: 'Failed to export .env' };
}
}
async loadSnapshots(): Promise<any[]> {
try {
const response = await fetch(`${this.config.apiBasePath}/snapshots`);
const data = await response.json();
this.state.snapshots = data;
return this.state.snapshots;
} catch (error) {
console.error('Failed to load snapshots:', error);
return [];
}
}
async createSnapshot(name: string, description?: string): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/snapshots`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, description })
});
if (response.ok) {
await this.loadSnapshots();
return true;
}
return false;
} catch (error) {
console.error('Failed to create snapshot:', error);
return false;
}
}
async restoreSnapshot(id: string): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/snapshots/${id}/restore`, {
method: 'POST'
});
if (response.ok) {
await this.loadVariables();
await this.loadSnapshots();
return true;
}
return false;
} catch (error) {
console.error('Failed to restore snapshot:', error);
return false;
}
}
async deleteSnapshot(id: string): Promise<boolean> {
try {
const response = await fetch(`${this.config.apiBasePath}/snapshots/${id}`, {
method: 'DELETE'
});
if (response.ok) {
await this.loadSnapshots();
return true;
}
return false;
} catch (error) {
console.error('Failed to delete snapshot:', error);
return false;
}
}
async loadProjectStatus(): Promise<any> {
try {
const response = await fetch(`${this.config.apiBasePath}/project/status`);
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to load project status:', error);
return null;
}
}
async getVariableConfig(variableName: string): Promise<any> {
try {
const response = await fetch(`${this.config.apiBasePath}/project/variable-config`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ variableName })
});
if (response.ok) {
return await response.json();
}
return null;
} catch (error) {
console.error('Failed to get variable config:', error);
return null;
}
}
// Getters for state
get isAuthenticated(): boolean {
return this.state.authenticated;
}
get variables(): DatabaseVariable[] {
return this.state.variables;
}
get snapshots(): any[] {
return this.state.snapshots;
}
get currentTab(): string {
return this.state.currentTab;
}
set currentTab(tab: string) {
this.state.currentTab = tab;
}
// Utility methods
getFilteredVariables(searchTerm: string = '', category: string = ''): DatabaseVariable[] {
return this.state.variables.filter(variable => {
const matchesSearch = variable.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(variable.description && variable.description.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesCategory = !category || variable.category === category;
return matchesSearch && matchesCategory;
});
}
getVariablesByCategory(): Record<string, DatabaseVariable[]> {
const categories: Record<string, DatabaseVariable[]> = {};
for (const variable of this.state.variables) {
const category = variable.category || 'Other';
if (!categories[category]) {
categories[category] = [];
}
categories[category].push(variable);
}
return categories;
}
generateSecret(length: number = 32): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
}
// Export a singleton instance for convenience
export const envManager = new EnvManager();
// Export the class for custom instances
export default EnvManager;