holosphere 1.1.19 → 2.0.0-alpha0
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/.env.example +36 -0
- package/.eslintrc.json +16 -0
- package/.prettierrc.json +7 -0
- package/README.md +476 -531
- package/bin/holosphere-activitypub.js +158 -0
- package/cleanup-test-data.js +204 -0
- package/examples/demo.html +1333 -0
- package/examples/example-bot.js +197 -0
- package/package.json +47 -87
- package/scripts/check-bundle-size.js +54 -0
- package/scripts/check-quest-ids.js +77 -0
- package/scripts/import-holons.js +578 -0
- package/scripts/publish-to-relay.js +101 -0
- package/scripts/read-example.js +186 -0
- package/scripts/relay-diagnostic.js +59 -0
- package/scripts/relay-example.js +179 -0
- package/scripts/resync-to-relay.js +245 -0
- package/scripts/revert-import.js +196 -0
- package/scripts/test-hybrid-mode.js +108 -0
- package/scripts/test-local-storage.js +63 -0
- package/scripts/test-nostr-direct.js +55 -0
- package/scripts/test-read-data.js +45 -0
- package/scripts/test-write-read.js +63 -0
- package/scripts/verify-import.js +95 -0
- package/scripts/verify-relay-data.js +139 -0
- package/src/ai/aggregation.js +319 -0
- package/src/ai/breakdown.js +511 -0
- package/src/ai/classifier.js +217 -0
- package/src/ai/council.js +228 -0
- package/src/ai/embeddings.js +279 -0
- package/src/ai/federation-ai.js +324 -0
- package/src/ai/h3-ai.js +955 -0
- package/src/ai/index.js +112 -0
- package/src/ai/json-ops.js +225 -0
- package/src/ai/llm-service.js +205 -0
- package/src/ai/nl-query.js +223 -0
- package/src/ai/relationships.js +353 -0
- package/src/ai/schema-extractor.js +218 -0
- package/src/ai/spatial.js +293 -0
- package/src/ai/tts.js +194 -0
- package/src/content/social-protocols.js +168 -0
- package/src/core/holosphere.js +273 -0
- package/src/crypto/secp256k1.js +259 -0
- package/src/federation/discovery.js +334 -0
- package/src/federation/hologram.js +1042 -0
- package/src/federation/registry.js +386 -0
- package/src/hierarchical/upcast.js +110 -0
- package/src/index.js +2669 -0
- package/src/schema/validator.js +91 -0
- package/src/spatial/h3-operations.js +110 -0
- package/src/storage/backend-factory.js +125 -0
- package/src/storage/backend-interface.js +142 -0
- package/src/storage/backends/activitypub/server.js +653 -0
- package/src/storage/backends/activitypub-backend.js +272 -0
- package/src/storage/backends/gundb-backend.js +233 -0
- package/src/storage/backends/nostr-backend.js +136 -0
- package/src/storage/filesystem-storage-browser.js +41 -0
- package/src/storage/filesystem-storage.js +138 -0
- package/src/storage/global-tables.js +81 -0
- package/src/storage/gun-async.js +281 -0
- package/src/storage/gun-wrapper.js +221 -0
- package/src/storage/indexeddb-storage.js +122 -0
- package/src/storage/key-storage-simple.js +76 -0
- package/src/storage/key-storage.js +136 -0
- package/src/storage/memory-storage.js +59 -0
- package/src/storage/migration.js +338 -0
- package/src/storage/nostr-async.js +811 -0
- package/src/storage/nostr-client.js +939 -0
- package/src/storage/nostr-wrapper.js +211 -0
- package/src/storage/outbox-queue.js +208 -0
- package/src/storage/persistent-storage.js +109 -0
- package/src/storage/sync-service.js +164 -0
- package/src/subscriptions/manager.js +142 -0
- package/test-ai-real-api.js +202 -0
- package/tests/unit/ai/aggregation.test.js +295 -0
- package/tests/unit/ai/breakdown.test.js +446 -0
- package/tests/unit/ai/classifier.test.js +294 -0
- package/tests/unit/ai/council.test.js +262 -0
- package/tests/unit/ai/embeddings.test.js +384 -0
- package/tests/unit/ai/federation-ai.test.js +344 -0
- package/tests/unit/ai/h3-ai.test.js +458 -0
- package/tests/unit/ai/index.test.js +304 -0
- package/tests/unit/ai/json-ops.test.js +307 -0
- package/tests/unit/ai/llm-service.test.js +390 -0
- package/tests/unit/ai/nl-query.test.js +383 -0
- package/tests/unit/ai/relationships.test.js +311 -0
- package/tests/unit/ai/schema-extractor.test.js +384 -0
- package/tests/unit/ai/spatial.test.js +279 -0
- package/tests/unit/ai/tts.test.js +279 -0
- package/tests/unit/content.test.js +332 -0
- package/tests/unit/contract/core.test.js +88 -0
- package/tests/unit/contract/crypto.test.js +198 -0
- package/tests/unit/contract/data.test.js +223 -0
- package/tests/unit/contract/federation.test.js +181 -0
- package/tests/unit/contract/hierarchical.test.js +113 -0
- package/tests/unit/contract/schema.test.js +114 -0
- package/tests/unit/contract/social.test.js +217 -0
- package/tests/unit/contract/spatial.test.js +110 -0
- package/tests/unit/contract/subscriptions.test.js +128 -0
- package/tests/unit/contract/utils.test.js +159 -0
- package/tests/unit/core.test.js +152 -0
- package/tests/unit/crypto.test.js +328 -0
- package/tests/unit/federation.test.js +234 -0
- package/tests/unit/gun-async.test.js +252 -0
- package/tests/unit/hierarchical.test.js +399 -0
- package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
- package/tests/unit/integration/scenario-02-federation.test.js +76 -0
- package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
- package/tests/unit/integration/scenario-04-validation.test.js +129 -0
- package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
- package/tests/unit/integration/scenario-06-social.test.js +135 -0
- package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
- package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
- package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
- package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
- package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
- package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
- package/tests/unit/performance/benchmark.test.js +85 -0
- package/tests/unit/schema.test.js +213 -0
- package/tests/unit/spatial.test.js +158 -0
- package/tests/unit/storage.test.js +195 -0
- package/tests/unit/subscriptions.test.js +328 -0
- package/tests/unit/test-data-permanence-debug.js +197 -0
- package/tests/unit/test-data-permanence.js +340 -0
- package/tests/unit/test-key-persistence-fixed.js +148 -0
- package/tests/unit/test-key-persistence.js +172 -0
- package/tests/unit/test-relay-permanence.js +376 -0
- package/tests/unit/test-second-node.js +95 -0
- package/tests/unit/test-simple-write.js +89 -0
- package/vite.config.js +49 -0
- package/vitest.config.js +20 -0
- package/FEDERATION.md +0 -213
- package/compute.js +0 -298
- package/content.js +0 -1022
- package/federation.js +0 -1234
- package/global.js +0 -736
- package/hexlib.js +0 -335
- package/hologram.js +0 -183
- package/holosphere-bundle.esm.js +0 -34549
- package/holosphere-bundle.js +0 -34580
- package/holosphere-bundle.min.js +0 -49
- package/holosphere.d.ts +0 -604
- package/holosphere.js +0 -739
- package/node.js +0 -246
- package/schema.js +0 -139
- package/utils.js +0 -302
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HoloSphere ActivityPub Server CLI
|
|
5
|
+
*
|
|
6
|
+
* Starts a self-hosted ActivityPub server for HoloSphere federation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* holosphere-activitypub [options]
|
|
10
|
+
*
|
|
11
|
+
* Options:
|
|
12
|
+
* -p, --port <port> Port to listen on (default: 3000)
|
|
13
|
+
* -d, --domain <domain> Domain name for federation (default: localhost)
|
|
14
|
+
* --data-dir <dir> Data directory for storage
|
|
15
|
+
* --protocol <protocol> Protocol: http or https (default: http)
|
|
16
|
+
* -h, --help Show this help message
|
|
17
|
+
*
|
|
18
|
+
* Environment Variables:
|
|
19
|
+
* HOLOSPHERE_AP_PORT Port to listen on
|
|
20
|
+
* HOLOSPHERE_AP_DOMAIN Domain name
|
|
21
|
+
* HOLOSPHERE_AP_DATADIR Data directory
|
|
22
|
+
* HOLOSPHERE_AP_PROTOCOL Protocol (http/https)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { ActivityPubServer } from '../src/storage/backends/activitypub/server.js';
|
|
26
|
+
|
|
27
|
+
function parseArgs(args) {
|
|
28
|
+
const config = {
|
|
29
|
+
port: parseInt(process.env.HOLOSPHERE_AP_PORT) || 3000,
|
|
30
|
+
domain: process.env.HOLOSPHERE_AP_DOMAIN || 'localhost',
|
|
31
|
+
dataDir: process.env.HOLOSPHERE_AP_DATADIR,
|
|
32
|
+
protocol: process.env.HOLOSPHERE_AP_PROTOCOL || 'http',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < args.length; i++) {
|
|
36
|
+
const arg = args[i];
|
|
37
|
+
const next = args[i + 1];
|
|
38
|
+
|
|
39
|
+
switch (arg) {
|
|
40
|
+
case '-p':
|
|
41
|
+
case '--port':
|
|
42
|
+
config.port = parseInt(next);
|
|
43
|
+
i++;
|
|
44
|
+
break;
|
|
45
|
+
case '-d':
|
|
46
|
+
case '--domain':
|
|
47
|
+
config.domain = next;
|
|
48
|
+
i++;
|
|
49
|
+
break;
|
|
50
|
+
case '--data-dir':
|
|
51
|
+
config.dataDir = next;
|
|
52
|
+
i++;
|
|
53
|
+
break;
|
|
54
|
+
case '--protocol':
|
|
55
|
+
config.protocol = next;
|
|
56
|
+
i++;
|
|
57
|
+
break;
|
|
58
|
+
case '-h':
|
|
59
|
+
case '--help':
|
|
60
|
+
showHelp();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
default:
|
|
63
|
+
if (arg.startsWith('-')) {
|
|
64
|
+
console.error(`Unknown option: ${arg}`);
|
|
65
|
+
showHelp();
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return config;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function showHelp() {
|
|
75
|
+
console.log(`
|
|
76
|
+
HoloSphere ActivityPub Server
|
|
77
|
+
|
|
78
|
+
Starts a self-hosted ActivityPub server for HoloSphere federation.
|
|
79
|
+
This server can federate with Mastodon, Pleroma, and other ActivityPub servers.
|
|
80
|
+
|
|
81
|
+
Usage:
|
|
82
|
+
holosphere-activitypub [options]
|
|
83
|
+
|
|
84
|
+
Options:
|
|
85
|
+
-p, --port <port> Port to listen on (default: 3000)
|
|
86
|
+
-d, --domain <domain> Domain name for federation (default: localhost)
|
|
87
|
+
--data-dir <dir> Data directory for persistent storage
|
|
88
|
+
--protocol <protocol> Protocol: http or https (default: http)
|
|
89
|
+
-h, --help Show this help message
|
|
90
|
+
|
|
91
|
+
Environment Variables:
|
|
92
|
+
HOLOSPHERE_AP_PORT Port to listen on
|
|
93
|
+
HOLOSPHERE_AP_DOMAIN Domain name
|
|
94
|
+
HOLOSPHERE_AP_DATADIR Data directory
|
|
95
|
+
HOLOSPHERE_AP_PROTOCOL Protocol (http/https)
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
# Start server on default port
|
|
99
|
+
holosphere-activitypub
|
|
100
|
+
|
|
101
|
+
# Start server on port 8080 with custom domain
|
|
102
|
+
holosphere-activitypub -p 8080 -d myholosphere.example.com
|
|
103
|
+
|
|
104
|
+
# Start with persistent data directory
|
|
105
|
+
holosphere-activitypub --data-dir ./data --domain myserver.local
|
|
106
|
+
|
|
107
|
+
Federation Notes:
|
|
108
|
+
For federation with other ActivityPub servers (Mastodon, etc.):
|
|
109
|
+
1. Your server must be accessible via the domain you specify
|
|
110
|
+
2. HTTPS is recommended for production federation
|
|
111
|
+
3. Set up proper DNS records pointing to your server
|
|
112
|
+
4. WebFinger discovery is available at /.well-known/webfinger
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
const args = process.argv.slice(2);
|
|
118
|
+
const config = parseArgs(args);
|
|
119
|
+
|
|
120
|
+
console.log('\n========================================');
|
|
121
|
+
console.log(' HoloSphere ActivityPub Server');
|
|
122
|
+
console.log('========================================');
|
|
123
|
+
console.log(` Port: ${config.port}`);
|
|
124
|
+
console.log(` Domain: ${config.domain}`);
|
|
125
|
+
console.log(` Protocol: ${config.protocol}`);
|
|
126
|
+
console.log(` Data Dir: ${config.dataDir || '(default)'}`);
|
|
127
|
+
console.log('========================================\n');
|
|
128
|
+
|
|
129
|
+
const server = new ActivityPubServer(config);
|
|
130
|
+
|
|
131
|
+
// Handle shutdown gracefully
|
|
132
|
+
process.on('SIGINT', () => {
|
|
133
|
+
console.log('\nShutting down...');
|
|
134
|
+
server.stop();
|
|
135
|
+
process.exit(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
process.on('SIGTERM', () => {
|
|
139
|
+
console.log('\nShutting down...');
|
|
140
|
+
server.stop();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await server.start();
|
|
146
|
+
console.log('\nServer endpoints:');
|
|
147
|
+
console.log(` WebFinger: ${config.protocol}://${config.domain}:${config.port}/.well-known/webfinger`);
|
|
148
|
+
console.log(` Actors: ${config.protocol}://${config.domain}:${config.port}/actor/{name}`);
|
|
149
|
+
console.log(` API: ${config.protocol}://${config.domain}:${config.port}/api/data`);
|
|
150
|
+
console.log(` Health: ${config.protocol}://${config.domain}:${config.port}/health`);
|
|
151
|
+
console.log('\nPress Ctrl+C to stop the server\n');
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Failed to start server:', error);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
main();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleanup Test Data from Relays
|
|
3
|
+
*
|
|
4
|
+
* This script finds and deletes test data published to relays
|
|
5
|
+
* It uses tombstone deletion (Nostr standard) to mark events as deleted
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { HoloSphere } from './dist/esm/holosphere.js';
|
|
9
|
+
|
|
10
|
+
// Relay configuration - use env vars or these defaults
|
|
11
|
+
const RELAYS = process.env.HOLOSPHERE_RELAYS?.split(',') || [
|
|
12
|
+
'wss://relay.holons.io',
|
|
13
|
+
'wss://relay.nostr.band',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const c = {
|
|
17
|
+
reset: '\x1b[0m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
blue: '\x1b[34m',
|
|
22
|
+
cyan: '\x1b[36m'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function log(msg, color = 'reset') {
|
|
26
|
+
console.log(`${c[color]}${msg}${c.reset}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('\n╔═══════════════════════════════════════════════════════════════════╗');
|
|
30
|
+
log('║ HOLOSPHERE TEST DATA CLEANUP UTILITY ║', 'cyan');
|
|
31
|
+
console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
|
|
32
|
+
|
|
33
|
+
// Test app names to clean up
|
|
34
|
+
const TEST_APPS = [
|
|
35
|
+
'permanence-test',
|
|
36
|
+
'debug-test',
|
|
37
|
+
'simple-test',
|
|
38
|
+
'relay-permanence-test',
|
|
39
|
+
'HolonsDebug'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
log('This script will delete test data from the following apps:', 'yellow');
|
|
43
|
+
TEST_APPS.forEach(app => log(` • ${app}`, 'blue'));
|
|
44
|
+
|
|
45
|
+
log('\n⚠️ Note: Deletion in Nostr is done by publishing tombstone events.', 'yellow');
|
|
46
|
+
log(' Old events may still exist on relays but will be marked as deleted.\n', 'yellow');
|
|
47
|
+
|
|
48
|
+
// Function to clean up a specific app
|
|
49
|
+
async function cleanupApp(appName) {
|
|
50
|
+
log(`\n${'─'.repeat(70)}`, 'cyan');
|
|
51
|
+
log(`Cleaning up: ${appName}`, 'cyan');
|
|
52
|
+
log('─'.repeat(70), 'cyan');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const hs = new HoloSphere({
|
|
56
|
+
appName: appName,
|
|
57
|
+
relays: RELAYS,
|
|
58
|
+
logLevel: 'WARN'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
log(`Public Key: ${hs.client.publicKey}`, 'blue');
|
|
62
|
+
|
|
63
|
+
// Wait for connections
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
65
|
+
|
|
66
|
+
// Query all events for this app
|
|
67
|
+
log('\nQuerying events...', 'yellow');
|
|
68
|
+
|
|
69
|
+
const filter = {
|
|
70
|
+
kinds: [30000],
|
|
71
|
+
authors: [hs.client.publicKey],
|
|
72
|
+
limit: 1000
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const events = await hs.client.query(filter, { timeout: 5000 });
|
|
76
|
+
|
|
77
|
+
// Filter to only this app's events
|
|
78
|
+
const appEvents = events.filter(event => {
|
|
79
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
80
|
+
return dTag && dTag[1] && dTag[1].startsWith(appName + '/');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
log(`Found ${appEvents.length} events for ${appName}`, 'blue');
|
|
84
|
+
|
|
85
|
+
if (appEvents.length === 0) {
|
|
86
|
+
log('No events to clean up', 'green');
|
|
87
|
+
return { deleted: 0, failed: 0 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Show sample of events
|
|
91
|
+
log('\nSample events:', 'yellow');
|
|
92
|
+
appEvents.slice(0, 5).forEach((event, i) => {
|
|
93
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
94
|
+
log(` ${i + 1}. ${dTag[1]}`, 'blue');
|
|
95
|
+
});
|
|
96
|
+
if (appEvents.length > 5) {
|
|
97
|
+
log(` ... and ${appEvents.length - 5} more`, 'blue');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log('\nDeleting events...', 'yellow');
|
|
101
|
+
|
|
102
|
+
let deleted = 0;
|
|
103
|
+
let failed = 0;
|
|
104
|
+
|
|
105
|
+
// Import storage utilities
|
|
106
|
+
const { nostrPut } = await import('./src/storage/nostr-async.js');
|
|
107
|
+
|
|
108
|
+
for (const event of appEvents) {
|
|
109
|
+
const dTag = event.tags.find(t => t[0] === 'd');
|
|
110
|
+
if (!dTag) continue;
|
|
111
|
+
|
|
112
|
+
const path = dTag[1];
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
// Create tombstone
|
|
116
|
+
const tombstone = {
|
|
117
|
+
_deleted: true,
|
|
118
|
+
_deletedAt: Date.now(),
|
|
119
|
+
_cleanedUp: true
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const result = await nostrPut(hs.client, path, tombstone);
|
|
123
|
+
const success = result.results.some(r => r.status === 'fulfilled');
|
|
124
|
+
|
|
125
|
+
if (success) {
|
|
126
|
+
deleted++;
|
|
127
|
+
log(` ✓ Deleted: ${path}`, 'green');
|
|
128
|
+
} else {
|
|
129
|
+
failed++;
|
|
130
|
+
log(` ✗ Failed: ${path}`, 'red');
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
failed++;
|
|
134
|
+
log(` ✗ Error deleting ${path}: ${error.message}`, 'red');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Rate limit to avoid overwhelming relays
|
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
log(`\n✓ Cleanup complete for ${appName}`, 'green');
|
|
142
|
+
log(` Deleted: ${deleted}`, 'green');
|
|
143
|
+
log(` Failed: ${failed}`, failed > 0 ? 'red' : 'green');
|
|
144
|
+
|
|
145
|
+
return { deleted, failed };
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
log(`\n✗ Error cleaning up ${appName}: ${error.message}`, 'red');
|
|
149
|
+
return { deleted: 0, failed: 1 };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Main cleanup function
|
|
154
|
+
async function cleanup() {
|
|
155
|
+
const results = {
|
|
156
|
+
totalDeleted: 0,
|
|
157
|
+
totalFailed: 0,
|
|
158
|
+
apps: []
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
for (const app of TEST_APPS) {
|
|
162
|
+
const result = await cleanupApp(app);
|
|
163
|
+
results.totalDeleted += result.deleted;
|
|
164
|
+
results.totalFailed += result.failed;
|
|
165
|
+
results.apps.push({ app, ...result });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Summary
|
|
169
|
+
log('\n' + '═'.repeat(70), 'cyan');
|
|
170
|
+
log(' CLEANUP SUMMARY', 'cyan');
|
|
171
|
+
log('═'.repeat(70), 'cyan');
|
|
172
|
+
|
|
173
|
+
results.apps.forEach(({ app, deleted, failed }) => {
|
|
174
|
+
if (deleted > 0 || failed > 0) {
|
|
175
|
+
log(`\n${app}:`, 'blue');
|
|
176
|
+
log(` Deleted: ${deleted}`, 'green');
|
|
177
|
+
if (failed > 0) log(` Failed: ${failed}`, 'red');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
log(`\nTotal Deleted: ${results.totalDeleted}`, 'green');
|
|
182
|
+
if (results.totalFailed > 0) {
|
|
183
|
+
log(`Total Failed: ${results.totalFailed}`, 'red');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (results.totalDeleted === 0) {
|
|
187
|
+
log('\n✓ No test data found to clean up', 'green');
|
|
188
|
+
} else {
|
|
189
|
+
log('\n✓ Test data cleanup complete!', 'green');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
log('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Run cleanup
|
|
196
|
+
cleanup()
|
|
197
|
+
.then(() => {
|
|
198
|
+
setTimeout(() => process.exit(0), 1000);
|
|
199
|
+
})
|
|
200
|
+
.catch(error => {
|
|
201
|
+
log(`\n✗ Fatal error: ${error.message}`, 'red');
|
|
202
|
+
console.error(error);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
});
|