@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.
- package/README.md +20 -0
- package/dist/scripts/setup-supabase.d.ts +8 -0
- package/dist/scripts/setup-supabase.d.ts.map +1 -0
- package/dist/scripts/setup-supabase.js +172 -0
- package/dist/scripts/setup-supabase.js.map +1 -0
- package/dist/scripts/test-remote-db.d.ts +8 -0
- package/dist/scripts/test-remote-db.d.ts.map +1 -0
- package/dist/scripts/test-remote-db.js +15 -0
- package/dist/scripts/test-remote-db.js.map +1 -0
- package/dist/scripts/utils/bypass-manager.d.ts +1 -1
- package/dist/scripts/utils/bypass-manager.d.ts.map +1 -1
- package/dist/scripts/utils/bypass-manager.js +29 -1
- package/dist/scripts/utils/bypass-manager.js.map +1 -1
- package/dist/scripts/utils/remote-logger.d.ts +49 -0
- package/dist/scripts/utils/remote-logger.d.ts.map +1 -0
- package/dist/scripts/utils/remote-logger.js +251 -0
- package/dist/scripts/utils/remote-logger.js.map +1 -0
- package/dist/scripts/view-bypass-log.js +35 -2
- package/dist/scripts/view-bypass-log.js.map +1 -1
- package/package.json +5 -1
- package/scripts/setup-supabase.ts +158 -0
- package/scripts/test-remote-db.ts +14 -0
- package/scripts/utils/bypass-manager.ts +30 -2
- package/scripts/utils/remote-logger.ts +256 -0
- package/scripts/view-bypass-log.ts +37 -2
|
@@ -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
|
+
});
|