@hatem427/code-guard-ci 1.0.6 → 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.
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ============================================================================
4
+ * remote-logger.ts — Remote database logging with Supabase
5
+ * ============================================================================
6
+ * Provides secure, tamper-proof logging by storing bypass records in Supabase.
7
+ * Falls back to local storage if remote DB is unavailable.
8
+ */
9
+
10
+ import { createClient, SupabaseClient } from '@supabase/supabase-js';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ // Load environment variables
15
+ import dotenv from 'dotenv';
16
+ dotenv.config();
17
+
18
+ export interface BypassEntry {
19
+ id?: string;
20
+ timestamp: string;
21
+ author: {
22
+ name: string;
23
+ email: string;
24
+ };
25
+ reason: string;
26
+ commitMessage: string;
27
+ branch: string;
28
+ files: string[];
29
+ method: 'commit-message' | 'env-variable' | 'password';
30
+ projectId: string;
31
+ }
32
+
33
+ export interface RemoteLoggerConfig {
34
+ supabaseUrl?: string;
35
+ supabaseKey?: string;
36
+ projectId?: string;
37
+ enabled: boolean;
38
+ }
39
+
40
+ let supabase: SupabaseClient | null = null;
41
+
42
+ /**
43
+ * Get configuration from environment variables
44
+ */
45
+ function getConfig(): RemoteLoggerConfig {
46
+ return {
47
+ supabaseUrl: process.env.CODE_GUARD_SUPABASE_URL,
48
+ supabaseKey: process.env.CODE_GUARD_SUPABASE_KEY,
49
+ projectId: process.env.CODE_GUARD_PROJECT_ID || getDefaultProjectId(),
50
+ enabled: !!(process.env.CODE_GUARD_SUPABASE_URL && process.env.CODE_GUARD_SUPABASE_KEY),
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Get default project ID from package.json or directory name
56
+ */
57
+ function getDefaultProjectId(): string {
58
+ try {
59
+ const packagePath = path.join(process.cwd(), 'package.json');
60
+ if (fs.existsSync(packagePath)) {
61
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
62
+ return pkg.name || path.basename(process.cwd());
63
+ }
64
+ } catch {
65
+ // Ignore errors
66
+ }
67
+ return path.basename(process.cwd());
68
+ }
69
+
70
+ /**
71
+ * Initialize Supabase client
72
+ */
73
+ function getSupabaseClient(): SupabaseClient | null {
74
+ if (supabase) return supabase;
75
+
76
+ const config = getConfig();
77
+ if (!config.enabled || !config.supabaseUrl || !config.supabaseKey) {
78
+ return null;
79
+ }
80
+
81
+ try {
82
+ supabase = createClient(config.supabaseUrl, config.supabaseKey);
83
+ return supabase;
84
+ } catch (error) {
85
+ console.error('Failed to initialize Supabase client:', error);
86
+ return null;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Record bypass to remote database
92
+ */
93
+ export async function recordBypassRemote(entry: BypassEntry): Promise<boolean> {
94
+ const client = getSupabaseClient();
95
+ const config = getConfig();
96
+
97
+ if (!client || !config.enabled) {
98
+ console.log('📝 Remote logging disabled - using local storage only');
99
+ return false;
100
+ }
101
+
102
+ try {
103
+ const { data, error } = await client
104
+ .from('bypass_logs')
105
+ .insert([
106
+ {
107
+ timestamp: entry.timestamp,
108
+ author_name: entry.author.name,
109
+ author_email: entry.author.email,
110
+ reason: entry.reason,
111
+ commit_message: entry.commitMessage,
112
+ branch: entry.branch,
113
+ files: entry.files,
114
+ method: entry.method,
115
+ project_id: config.projectId,
116
+ },
117
+ ])
118
+ .select();
119
+
120
+ if (error) {
121
+ console.error('❌ Failed to record bypass to remote DB:', error.message);
122
+ return false;
123
+ }
124
+
125
+ console.log('✅ Bypass recorded to remote database (ID: ' + data[0]?.id + ')');
126
+ return true;
127
+ } catch (error: any) {
128
+ console.error('❌ Remote logging error:', error.message);
129
+ return false;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Fetch all bypass logs from remote database
135
+ */
136
+ export async function fetchBypassLogs(projectId?: string): Promise<BypassEntry[]> {
137
+ const client = getSupabaseClient();
138
+ const config = getConfig();
139
+
140
+ if (!client || !config.enabled) {
141
+ console.error('❌ Remote database not configured');
142
+ return [];
143
+ }
144
+
145
+ try {
146
+ const query = client
147
+ .from('bypass_logs')
148
+ .select('*')
149
+ .order('timestamp', { ascending: false });
150
+
151
+ // Filter by project if specified
152
+ if (projectId || config.projectId) {
153
+ query.eq('project_id', projectId || config.projectId);
154
+ }
155
+
156
+ const { data, error } = await query;
157
+
158
+ if (error) {
159
+ console.error('❌ Failed to fetch logs from remote DB:', error.message);
160
+ return [];
161
+ }
162
+
163
+ // Transform to BypassEntry format
164
+ return (data || []).map((row: any) => ({
165
+ id: row.id,
166
+ timestamp: row.timestamp,
167
+ author: {
168
+ name: row.author_name,
169
+ email: row.author_email,
170
+ },
171
+ reason: row.reason,
172
+ commitMessage: row.commit_message,
173
+ branch: row.branch,
174
+ files: row.files,
175
+ method: row.method,
176
+ projectId: row.project_id,
177
+ }));
178
+ } catch (error: any) {
179
+ console.error('❌ Error fetching remote logs:', error.message);
180
+ return [];
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Delete bypass logs from remote database (admin only)
186
+ */
187
+ export async function deleteBypassLogsRemote(
188
+ ids: string[],
189
+ adminPassword: string
190
+ ): Promise<boolean> {
191
+ const client = getSupabaseClient();
192
+ const config = getConfig();
193
+
194
+ if (!client || !config.enabled) {
195
+ console.error('❌ Remote database not configured');
196
+ return false;
197
+ }
198
+
199
+ try {
200
+ // Verify admin password via RPC or custom logic
201
+ // For now, we'll just delete directly (add auth logic as needed)
202
+ const { error } = await client.from('bypass_logs').delete().in('id', ids);
203
+
204
+ if (error) {
205
+ console.error('❌ Failed to delete logs from remote DB:', error.message);
206
+ return false;
207
+ }
208
+
209
+ console.log(`✅ Deleted ${ids.length} log(s) from remote database`);
210
+ return true;
211
+ } catch (error: any) {
212
+ console.error('❌ Error deleting remote logs:', error.message);
213
+ return false;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Test remote database connection
219
+ */
220
+ export async function testConnection(): Promise<boolean> {
221
+ const client = getSupabaseClient();
222
+ const config = getConfig();
223
+
224
+ if (!client || !config.enabled) {
225
+ console.log('❌ Remote database not configured');
226
+ console.log('\nTo enable remote logging, add to your .env file:');
227
+ console.log(' CODE_GUARD_SUPABASE_URL=https://your-project.supabase.co');
228
+ console.log(' CODE_GUARD_SUPABASE_KEY=your-anon-key');
229
+ console.log(' CODE_GUARD_PROJECT_ID=your-project-name\n');
230
+ return false;
231
+ }
232
+
233
+ try {
234
+ const { error } = await client.from('bypass_logs').select('count', { count: 'exact', head: true });
235
+
236
+ if (error) {
237
+ console.error('❌ Connection test failed:', error.message);
238
+ return false;
239
+ }
240
+
241
+ console.log('✅ Remote database connection successful');
242
+ console.log(`📊 Project ID: ${config.projectId}`);
243
+ return true;
244
+ } catch (error: any) {
245
+ console.error('❌ Connection error:', error.message);
246
+ return false;
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Check if remote logging is enabled
252
+ */
253
+ export function isRemoteLoggingEnabled(): boolean {
254
+ const config = getConfig();
255
+ return config.enabled;
256
+ }
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { generateBypassReport, getBypassesByAuthor, getRecentBypasses } from './utils/bypass-manager';
14
+ import { fetchBypassLogs, isRemoteLoggingEnabled } from './utils/remote-logger';
14
15
  import * as logger from './utils/logger';
15
16
 
16
17
  function parseArgs(): { author?: string; days?: number } {
@@ -34,10 +35,41 @@ function parseArgs(): { author?: string; days?: number } {
34
35
  return opts;
35
36
  }
36
37
 
37
- function main() {
38
+ async function main() {
38
39
  logger.header('📊 Code Guardian — Bypass Audit Log');
39
40
 
40
41
  const opts = parseArgs();
42
+ const remoteEnabled = isRemoteLoggingEnabled();
43
+
44
+ // Try remote logs first
45
+ if (remoteEnabled) {
46
+ try {
47
+ console.log('📡 Fetching from remote database...\n');
48
+ const remoteLogs = await fetchBypassLogs();
49
+
50
+ if (remoteLogs.length === 0) {
51
+ console.log('✅ No bypasses recorded.\n');
52
+ return;
53
+ }
54
+
55
+ console.log(`Found ${remoteLogs.length} bypass(es):\n`);
56
+ remoteLogs.forEach((entry: any, idx: number) => {
57
+ const date = new Date(entry.timestamp).toLocaleString();
58
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
59
+ console.log(`${idx + 1}. ${entry.author.name} <${entry.author.email}>`);
60
+ console.log(` 📅 ${date}`);
61
+ console.log(` 📝 ${entry.reason}`);
62
+ console.log(` 🌿 ${entry.branch}`);
63
+ console.log(` 🔑 ${entry.method}`);
64
+ console.log(` 📁 ${entry.files.length} file(s)`);
65
+ console.log('');
66
+ });
67
+ return;
68
+ } catch (error: any) {
69
+ console.warn('⚠️ Failed to fetch remote logs:', error.message);
70
+ console.log('Falling back to local logs...\n');
71
+ }
72
+ }
41
73
 
42
74
  if (opts.author) {
43
75
  logger.info(`Filtering by author: ${opts.author}`);
@@ -89,4 +121,7 @@ function main() {
89
121
  }
90
122
  }
91
123
 
92
- main();
124
+ main().catch((err) => {
125
+ console.error('❌ Error:', err.message);
126
+ process.exit(1);
127
+ });