bunsane 0.1.1 → 0.1.2
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/core/App.ts +81 -32
- package/core/Entity.ts +34 -2
- package/core/Query.ts +57 -7
- package/core/RequestLoaders.ts +59 -35
- package/database/index.ts +5 -5
- package/gql/Generator.ts +2 -1
- package/gql/index.ts +11 -1
- package/package.json +16 -8
- package/tests/component.test.ts +134 -1
- package/examples/hooks/README.md +0 -228
- package/examples/hooks/audit-logger.ts +0 -495
- package/validate-docs.sh +0 -90
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
import { EntityHook, ComponentHook } from "../../core/decorators/EntityHooks";
|
|
2
|
-
import { EntityCreatedEvent, EntityUpdatedEvent, EntityDeletedEvent, ComponentAddedEvent, ComponentUpdatedEvent, ComponentRemovedEvent } from "../../core/events/EntityLifecycleEvents";
|
|
3
|
-
import { Entity } from "../../core/Entity";
|
|
4
|
-
import { logger } from "../../core/Logger";
|
|
5
|
-
|
|
6
|
-
export interface AuditLogEntry {
|
|
7
|
-
id: string;
|
|
8
|
-
timestamp: Date;
|
|
9
|
-
action: 'create' | 'update' | 'delete' | 'add_component' | 'update_component' | 'remove_component';
|
|
10
|
-
entityId: string;
|
|
11
|
-
entityType?: string;
|
|
12
|
-
componentType?: string;
|
|
13
|
-
userId?: string;
|
|
14
|
-
oldData?: any;
|
|
15
|
-
newData?: any;
|
|
16
|
-
metadata?: Record<string, any>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface AuditLoggerConfig {
|
|
20
|
-
enabled: boolean;
|
|
21
|
-
level: 'debug' | 'info' | 'warn' | 'error';
|
|
22
|
-
storage: 'memory' | 'database' | 'file' | 'external';
|
|
23
|
-
includeData: boolean;
|
|
24
|
-
maxEntries: number;
|
|
25
|
-
retentionDays: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* AuditLogger - Comprehensive audit logging for entity lifecycle events
|
|
30
|
-
*
|
|
31
|
-
* Features:
|
|
32
|
-
* - Logs all entity and component lifecycle events
|
|
33
|
-
* - Multiple storage backends (memory, database, file, external)
|
|
34
|
-
* - Configurable data inclusion
|
|
35
|
-
* - Automatic cleanup of old entries
|
|
36
|
-
* - Performance monitoring
|
|
37
|
-
* - User context tracking
|
|
38
|
-
*/
|
|
39
|
-
export class AuditLogger {
|
|
40
|
-
private logs: AuditLogEntry[] = [];
|
|
41
|
-
private config: AuditLoggerConfig;
|
|
42
|
-
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
43
|
-
|
|
44
|
-
constructor(config: Partial<AuditLoggerConfig> = {}) {
|
|
45
|
-
this.config = {
|
|
46
|
-
enabled: true,
|
|
47
|
-
level: 'info',
|
|
48
|
-
storage: 'memory',
|
|
49
|
-
includeData: true,
|
|
50
|
-
maxEntries: 10000,
|
|
51
|
-
retentionDays: 30,
|
|
52
|
-
...config
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
if (this.config.enabled) {
|
|
56
|
-
this.startCleanupInterval();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Log entity creation events
|
|
62
|
-
*/
|
|
63
|
-
@EntityHook("entity.created")
|
|
64
|
-
async handleEntityCreated(event: EntityCreatedEvent) {
|
|
65
|
-
if (!this.config.enabled) return;
|
|
66
|
-
|
|
67
|
-
const entry: AuditLogEntry = {
|
|
68
|
-
id: this.generateId(),
|
|
69
|
-
timestamp: new Date(),
|
|
70
|
-
action: 'create',
|
|
71
|
-
entityId: event.getEntity().id,
|
|
72
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
73
|
-
userId: this.getCurrentUserId(),
|
|
74
|
-
newData: this.config.includeData ? await this.extractEntityData(event.getEntity()) : undefined,
|
|
75
|
-
metadata: {
|
|
76
|
-
isNew: event.isNew,
|
|
77
|
-
source: 'entity_hook'
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
await this.storeLogEntry(entry);
|
|
82
|
-
this.log('info', `Entity created: ${entry.entityId}`, entry);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Log entity update events
|
|
87
|
-
*/
|
|
88
|
-
@EntityHook("entity.updated")
|
|
89
|
-
async handleEntityUpdated(event: EntityUpdatedEvent) {
|
|
90
|
-
if (!this.config.enabled) return;
|
|
91
|
-
|
|
92
|
-
const entry: AuditLogEntry = {
|
|
93
|
-
id: this.generateId(),
|
|
94
|
-
timestamp: new Date(),
|
|
95
|
-
action: 'update',
|
|
96
|
-
entityId: event.getEntity().id,
|
|
97
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
98
|
-
userId: this.getCurrentUserId(),
|
|
99
|
-
metadata: {
|
|
100
|
-
changedComponents: event.getChangedComponents(),
|
|
101
|
-
source: 'entity_hook'
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
await this.storeLogEntry(entry);
|
|
106
|
-
this.log('info', `Entity updated: ${entry.entityId}`, entry);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Log entity deletion events
|
|
111
|
-
*/
|
|
112
|
-
@EntityHook("entity.deleted")
|
|
113
|
-
async handleEntityDeleted(event: EntityDeletedEvent) {
|
|
114
|
-
if (!this.config.enabled) return;
|
|
115
|
-
|
|
116
|
-
const entry: AuditLogEntry = {
|
|
117
|
-
id: this.generateId(),
|
|
118
|
-
timestamp: new Date(),
|
|
119
|
-
action: 'delete',
|
|
120
|
-
entityId: event.getEntity().id,
|
|
121
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
122
|
-
userId: this.getCurrentUserId(),
|
|
123
|
-
oldData: this.config.includeData ? await this.extractEntityData(event.getEntity()) : undefined,
|
|
124
|
-
metadata: {
|
|
125
|
-
isSoftDelete: event.isSoftDelete,
|
|
126
|
-
source: 'entity_hook'
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
await this.storeLogEntry(entry);
|
|
131
|
-
this.log('info', `Entity deleted: ${entry.entityId}`, entry);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Log component addition events
|
|
136
|
-
*/
|
|
137
|
-
@ComponentHook("component.added")
|
|
138
|
-
async handleComponentAdded(event: ComponentAddedEvent) {
|
|
139
|
-
if (!this.config.enabled) return;
|
|
140
|
-
|
|
141
|
-
const entry: AuditLogEntry = {
|
|
142
|
-
id: this.generateId(),
|
|
143
|
-
timestamp: new Date(),
|
|
144
|
-
action: 'add_component',
|
|
145
|
-
entityId: event.getEntity().id,
|
|
146
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
147
|
-
componentType: event.getComponentType(),
|
|
148
|
-
userId: this.getCurrentUserId(),
|
|
149
|
-
newData: this.config.includeData ? event.getComponent().data() : undefined,
|
|
150
|
-
metadata: {
|
|
151
|
-
source: 'component_hook'
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
await this.storeLogEntry(entry);
|
|
156
|
-
this.log('debug', `Component added: ${entry.componentType} to ${entry.entityId}`, entry);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Log component update events
|
|
161
|
-
*/
|
|
162
|
-
@ComponentHook("component.updated")
|
|
163
|
-
async handleComponentUpdated(event: ComponentUpdatedEvent) {
|
|
164
|
-
if (!this.config.enabled) return;
|
|
165
|
-
|
|
166
|
-
const entry: AuditLogEntry = {
|
|
167
|
-
id: this.generateId(),
|
|
168
|
-
timestamp: new Date(),
|
|
169
|
-
action: 'update_component',
|
|
170
|
-
entityId: event.getEntity().id,
|
|
171
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
172
|
-
componentType: event.getComponentType(),
|
|
173
|
-
userId: this.getCurrentUserId(),
|
|
174
|
-
oldData: this.config.includeData ? event.getOldData() : undefined,
|
|
175
|
-
newData: this.config.includeData ? event.getNewData() : undefined,
|
|
176
|
-
metadata: {
|
|
177
|
-
source: 'component_hook'
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
await this.storeLogEntry(entry);
|
|
182
|
-
this.log('debug', `Component updated: ${entry.componentType} on ${entry.entityId}`, entry);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Log component removal events
|
|
187
|
-
*/
|
|
188
|
-
@ComponentHook("component.removed")
|
|
189
|
-
async handleComponentRemoved(event: ComponentRemovedEvent) {
|
|
190
|
-
if (!this.config.enabled) return;
|
|
191
|
-
|
|
192
|
-
const entry: AuditLogEntry = {
|
|
193
|
-
id: this.generateId(),
|
|
194
|
-
timestamp: new Date(),
|
|
195
|
-
action: 'remove_component',
|
|
196
|
-
entityId: event.getEntity().id,
|
|
197
|
-
entityType: this.getEntityType(event.getEntity()),
|
|
198
|
-
componentType: event.getComponentType(),
|
|
199
|
-
userId: this.getCurrentUserId(),
|
|
200
|
-
oldData: this.config.includeData ? event.getComponent().data() : undefined,
|
|
201
|
-
metadata: {
|
|
202
|
-
source: 'component_hook'
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
await this.storeLogEntry(entry);
|
|
207
|
-
this.log('debug', `Component removed: ${entry.componentType} from ${entry.entityId}`, entry);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Get all audit logs
|
|
212
|
-
*/
|
|
213
|
-
getLogs(filter?: {
|
|
214
|
-
entityId?: string;
|
|
215
|
-
action?: string;
|
|
216
|
-
userId?: string;
|
|
217
|
-
since?: Date;
|
|
218
|
-
until?: Date;
|
|
219
|
-
}): AuditLogEntry[] {
|
|
220
|
-
let filteredLogs = [...this.logs];
|
|
221
|
-
|
|
222
|
-
if (filter) {
|
|
223
|
-
if (filter.entityId) {
|
|
224
|
-
filteredLogs = filteredLogs.filter(log => log.entityId === filter.entityId);
|
|
225
|
-
}
|
|
226
|
-
if (filter.action) {
|
|
227
|
-
filteredLogs = filteredLogs.filter(log => log.action === filter.action);
|
|
228
|
-
}
|
|
229
|
-
if (filter.userId) {
|
|
230
|
-
filteredLogs = filteredLogs.filter(log => log.userId === filter.userId);
|
|
231
|
-
}
|
|
232
|
-
if (filter.since) {
|
|
233
|
-
filteredLogs = filteredLogs.filter(log => log.timestamp >= filter.since!);
|
|
234
|
-
}
|
|
235
|
-
if (filter.until) {
|
|
236
|
-
filteredLogs = filteredLogs.filter(log => log.timestamp <= filter.until!);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return filteredLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Get audit logs for a specific entity
|
|
245
|
-
*/
|
|
246
|
-
getEntityLogs(entityId: string): AuditLogEntry[] {
|
|
247
|
-
return this.getLogs({ entityId });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Get audit logs for a specific user
|
|
252
|
-
*/
|
|
253
|
-
getUserLogs(userId: string): AuditLogEntry[] {
|
|
254
|
-
return this.getLogs({ userId });
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Clear old audit logs based on retention policy
|
|
259
|
-
*/
|
|
260
|
-
async clearOldLogs(): Promise<void> {
|
|
261
|
-
const cutoffDate = new Date();
|
|
262
|
-
cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);
|
|
263
|
-
|
|
264
|
-
const initialCount = this.logs.length;
|
|
265
|
-
this.logs = this.logs.filter(log => log.timestamp >= cutoffDate);
|
|
266
|
-
|
|
267
|
-
const removedCount = initialCount - this.logs.length;
|
|
268
|
-
if (removedCount > 0) {
|
|
269
|
-
this.log('info', `Cleared ${removedCount} old audit log entries`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Export audit logs to different formats
|
|
275
|
-
*/
|
|
276
|
-
exportLogs(format: 'json' | 'csv' = 'json'): string {
|
|
277
|
-
switch (format) {
|
|
278
|
-
case 'json':
|
|
279
|
-
return JSON.stringify(this.logs, null, 2);
|
|
280
|
-
case 'csv':
|
|
281
|
-
return this.convertToCSV(this.logs);
|
|
282
|
-
default:
|
|
283
|
-
throw new Error(`Unsupported export format: ${format}`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Get audit statistics
|
|
289
|
-
*/
|
|
290
|
-
getStatistics(): {
|
|
291
|
-
totalEntries: number;
|
|
292
|
-
entriesByAction: Record<string, number>;
|
|
293
|
-
entriesByEntityType: Record<string, number>;
|
|
294
|
-
oldestEntry: Date | null;
|
|
295
|
-
newestEntry: Date | null;
|
|
296
|
-
averageEntriesPerDay: number;
|
|
297
|
-
} {
|
|
298
|
-
const stats = {
|
|
299
|
-
totalEntries: this.logs.length,
|
|
300
|
-
entriesByAction: {} as Record<string, number>,
|
|
301
|
-
entriesByEntityType: {} as Record<string, number>,
|
|
302
|
-
oldestEntry: null as Date | null,
|
|
303
|
-
newestEntry: null as Date | null,
|
|
304
|
-
averageEntriesPerDay: 0
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
if (this.logs.length === 0) return stats;
|
|
308
|
-
|
|
309
|
-
// Calculate distributions
|
|
310
|
-
for (const log of this.logs) {
|
|
311
|
-
stats.entriesByAction[log.action] = (stats.entriesByAction[log.action] || 0) + 1;
|
|
312
|
-
if (log.entityType) {
|
|
313
|
-
stats.entriesByEntityType[log.entityType] = (stats.entriesByEntityType[log.entityType] || 0) + 1;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Calculate date range
|
|
318
|
-
const timestamps = this.logs.map(log => log.timestamp.getTime()).sort();
|
|
319
|
-
stats.oldestEntry = new Date(timestamps[0]!);
|
|
320
|
-
stats.newestEntry = new Date(timestamps[timestamps.length - 1]!);
|
|
321
|
-
|
|
322
|
-
// Calculate average entries per day
|
|
323
|
-
const daysDiff = (stats.newestEntry.getTime() - stats.oldestEntry.getTime()) / (1000 * 60 * 60 * 24);
|
|
324
|
-
stats.averageEntriesPerDay = daysDiff > 0 ? stats.totalEntries / daysDiff : stats.totalEntries;
|
|
325
|
-
|
|
326
|
-
return stats;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Store log entry based on configured storage backend
|
|
331
|
-
*/
|
|
332
|
-
private async storeLogEntry(entry: AuditLogEntry): Promise<void> {
|
|
333
|
-
try {
|
|
334
|
-
switch (this.config.storage) {
|
|
335
|
-
case 'memory':
|
|
336
|
-
this.storeInMemory(entry);
|
|
337
|
-
break;
|
|
338
|
-
case 'database':
|
|
339
|
-
await this.storeInDatabase(entry);
|
|
340
|
-
break;
|
|
341
|
-
case 'file':
|
|
342
|
-
await this.storeInFile(entry);
|
|
343
|
-
break;
|
|
344
|
-
case 'external':
|
|
345
|
-
await this.storeExternally(entry);
|
|
346
|
-
break;
|
|
347
|
-
default:
|
|
348
|
-
throw new Error(`Unsupported storage backend: ${this.config.storage}`);
|
|
349
|
-
}
|
|
350
|
-
} catch (error) {
|
|
351
|
-
this.log('error', `Failed to store audit log entry: ${error}`);
|
|
352
|
-
// Fallback to memory storage
|
|
353
|
-
this.storeInMemory(entry);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private storeInMemory(entry: AuditLogEntry): void {
|
|
358
|
-
this.logs.push(entry);
|
|
359
|
-
|
|
360
|
-
// Enforce max entries limit
|
|
361
|
-
if (this.logs.length > this.config.maxEntries) {
|
|
362
|
-
this.logs = this.logs.slice(-this.config.maxEntries);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
private async storeInDatabase(entry: AuditLogEntry): Promise<void> {
|
|
367
|
-
// Implementation for database storage
|
|
368
|
-
// This would integrate with your database layer
|
|
369
|
-
throw new Error("Database storage not implemented - extend this method");
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
private async storeInFile(entry: AuditLogEntry): Promise<void> {
|
|
373
|
-
// Implementation for file storage
|
|
374
|
-
// This would write to log files
|
|
375
|
-
throw new Error("File storage not implemented - extend this method");
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private async storeExternally(entry: AuditLogEntry): Promise<void> {
|
|
379
|
-
// Implementation for external service (e.g., Logstash, Splunk)
|
|
380
|
-
// This would send to external logging service
|
|
381
|
-
throw new Error("External storage not implemented - extend this method");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private startCleanupInterval(): void {
|
|
385
|
-
// Run cleanup daily
|
|
386
|
-
this.cleanupInterval = setInterval(() => {
|
|
387
|
-
this.clearOldLogs().catch(error => {
|
|
388
|
-
this.log('error', `Failed to clear old audit logs: ${error}`);
|
|
389
|
-
});
|
|
390
|
-
}, 24 * 60 * 60 * 1000); // 24 hours
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private stopCleanupInterval(): void {
|
|
394
|
-
if (this.cleanupInterval) {
|
|
395
|
-
clearInterval(this.cleanupInterval);
|
|
396
|
-
this.cleanupInterval = null;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
private generateId(): string {
|
|
401
|
-
return `audit_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
private getEntityType(entity: Entity): string | undefined {
|
|
405
|
-
// Try to determine entity type from components
|
|
406
|
-
// This is a simple implementation - you might want to enhance this
|
|
407
|
-
try {
|
|
408
|
-
const components = entity.componentList();
|
|
409
|
-
return components.length > 0 ? components[0]?.constructor.name : 'unknown';
|
|
410
|
-
} catch {
|
|
411
|
-
return 'unknown';
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private getCurrentUserId(): string | undefined {
|
|
416
|
-
// Implementation to get current user from context
|
|
417
|
-
// This depends on your authentication system
|
|
418
|
-
return 'system'; // Default to system user
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
private async extractEntityData(entity: Entity): Promise<any> {
|
|
422
|
-
try {
|
|
423
|
-
const data: any = {};
|
|
424
|
-
const components = entity.componentList();
|
|
425
|
-
|
|
426
|
-
for (const component of components) {
|
|
427
|
-
try {
|
|
428
|
-
const componentData = await entity.get(component.constructor as any);
|
|
429
|
-
if (componentData) {
|
|
430
|
-
data[component.constructor.name] = componentData;
|
|
431
|
-
}
|
|
432
|
-
} catch (error) {
|
|
433
|
-
// Skip components that can't be retrieved
|
|
434
|
-
this.log('debug', `Could not extract data for component ${component.constructor.name}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return data;
|
|
439
|
-
} catch (error) {
|
|
440
|
-
this.log('warn', `Failed to extract entity data: ${error}`);
|
|
441
|
-
return { error: 'Failed to extract data' };
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private convertToCSV(logs: AuditLogEntry[]): string {
|
|
446
|
-
const headers = [
|
|
447
|
-
'id', 'timestamp', 'action', 'entityId', 'entityType',
|
|
448
|
-
'componentType', 'userId', 'oldData', 'newData', 'metadata'
|
|
449
|
-
];
|
|
450
|
-
|
|
451
|
-
const rows = logs.map(log => [
|
|
452
|
-
log.id,
|
|
453
|
-
log.timestamp.toISOString(),
|
|
454
|
-
log.action,
|
|
455
|
-
log.entityId,
|
|
456
|
-
log.entityType || '',
|
|
457
|
-
log.componentType || '',
|
|
458
|
-
log.userId || '',
|
|
459
|
-
log.oldData ? JSON.stringify(log.oldData) : '',
|
|
460
|
-
log.newData ? JSON.stringify(log.newData) : '',
|
|
461
|
-
log.metadata ? JSON.stringify(log.metadata) : ''
|
|
462
|
-
]);
|
|
463
|
-
|
|
464
|
-
return [headers, ...rows]
|
|
465
|
-
.map(row => row.map(field => `"${field.replace(/"/g, '""')}"`).join(','))
|
|
466
|
-
.join('\n');
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private log(level: string, message: string, data?: any): void {
|
|
470
|
-
const fullMessage = data ? `${message} ${JSON.stringify(data)}` : message;
|
|
471
|
-
|
|
472
|
-
switch (level) {
|
|
473
|
-
case 'debug':
|
|
474
|
-
logger.debug(fullMessage);
|
|
475
|
-
break;
|
|
476
|
-
case 'info':
|
|
477
|
-
logger.info(fullMessage);
|
|
478
|
-
break;
|
|
479
|
-
case 'warn':
|
|
480
|
-
logger.warn(fullMessage);
|
|
481
|
-
break;
|
|
482
|
-
case 'error':
|
|
483
|
-
logger.error(fullMessage);
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Cleanup resources
|
|
490
|
-
*/
|
|
491
|
-
destroy(): void {
|
|
492
|
-
this.stopCleanupInterval();
|
|
493
|
-
this.logs = [];
|
|
494
|
-
}
|
|
495
|
-
}
|
package/validate-docs.sh
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
|
|
3
|
-
# BunSane Documentation Validation Script
|
|
4
|
-
# This script validates the basic functionality of the documentation site
|
|
5
|
-
|
|
6
|
-
echo "🔍 Validating BunSane Documentation Site..."
|
|
7
|
-
echo "=========================================="
|
|
8
|
-
|
|
9
|
-
# Check if docs directory exists
|
|
10
|
-
if [ ! -d "docs" ]; then
|
|
11
|
-
echo "❌ docs directory not found!"
|
|
12
|
-
exit 1
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
echo "✅ docs directory exists"
|
|
16
|
-
|
|
17
|
-
# Check for required files
|
|
18
|
-
required_files=("index.html" "_sidebar.md" "README.md" "_coverpage.md" "getting-started.md")
|
|
19
|
-
for file in "${required_files[@]}"; do
|
|
20
|
-
if [ -f "docs/$file" ]; then
|
|
21
|
-
echo "✅ $file exists"
|
|
22
|
-
else
|
|
23
|
-
echo "❌ $file missing"
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
done
|
|
27
|
-
|
|
28
|
-
# Check for core concepts directory
|
|
29
|
-
if [ -d "docs/core-concepts" ]; then
|
|
30
|
-
echo "✅ core-concepts directory exists"
|
|
31
|
-
else
|
|
32
|
-
echo "❌ core-concepts directory missing"
|
|
33
|
-
exit 1
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Check for entity documentation
|
|
37
|
-
if [ -f "docs/core-concepts/entity.md" ]; then
|
|
38
|
-
echo "✅ entity.md exists"
|
|
39
|
-
else
|
|
40
|
-
echo "❌ entity.md missing"
|
|
41
|
-
exit 1
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
# Validate HTML structure
|
|
45
|
-
if grep -q "<!DOCTYPE html>" docs/index.html; then
|
|
46
|
-
echo "✅ index.html has valid HTML structure"
|
|
47
|
-
else
|
|
48
|
-
echo "❌ index.html missing DOCTYPE"
|
|
49
|
-
exit 1
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
# Check for Docsify configuration
|
|
53
|
-
if grep -q "window.\$docsify" docs/index.html; then
|
|
54
|
-
echo "✅ Docsify configuration found"
|
|
55
|
-
else
|
|
56
|
-
echo "❌ Docsify configuration missing"
|
|
57
|
-
exit 1
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
# Check sidebar structure
|
|
61
|
-
if grep -q "\- \*\*Getting Started\*\*" docs/_sidebar.md; then
|
|
62
|
-
echo "✅ Sidebar has Getting Started section"
|
|
63
|
-
else
|
|
64
|
-
echo "❌ Sidebar missing Getting Started section"
|
|
65
|
-
exit 1
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
# Check for navigation links
|
|
69
|
-
if grep -q "\[Entity System\]" docs/_sidebar.md; then
|
|
70
|
-
echo "✅ Sidebar has Entity System link"
|
|
71
|
-
else
|
|
72
|
-
echo "❌ Sidebar missing Entity System link"
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
echo ""
|
|
77
|
-
echo "🎉 All basic validations passed!"
|
|
78
|
-
echo "=================================="
|
|
79
|
-
echo "Your BunSane documentation site is ready for deployment."
|
|
80
|
-
echo ""
|
|
81
|
-
echo "Next steps:"
|
|
82
|
-
echo "1. Enable GitHub Pages in your repository settings"
|
|
83
|
-
echo "2. Set the source to 'Deploy from a branch'"
|
|
84
|
-
echo "3. Select the 'gh-pages' branch (will be created by GitHub Actions)"
|
|
85
|
-
echo "4. Your documentation will be available at: https://yaaruu.github.io/bunsane/"
|
|
86
|
-
echo ""
|
|
87
|
-
echo "For local development:"
|
|
88
|
-
echo "1. Install docsify-cli: npm install -g docsify-cli"
|
|
89
|
-
echo "2. Run: docsify serve docs"
|
|
90
|
-
echo "3. Open http://localhost:3000"
|