From 4b8725ac06203f8d202aa27c0636072f4dd197b9 Mon Sep 17 00:00:00 2001 From: 12inchpenguin Date: Sun, 6 Apr 2025 19:02:22 +0300 Subject: [PATCH] bug fixes and updates --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/cli.ts | 2 - src/commands/upload.ts | 10 +- src/services/network.ts | 291 +++++++++++++++++++++++++++++++++++----- src/utils/config.ts | 2 +- 6 files changed, 273 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index e572109..29e4377 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "author": "Debros", "license": "gnu-gpl-v3.0", "dependencies": { - "@debros/network": "^0.0.14-alpha", + "@debros/network": "^0.0.16-alpha", "@ipshipyard/node-datachannel": "0.26.5", "@solana/web3.js": "^1.87.6", "chalk": "^5.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index beddf3b..3b88cdd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@debros/network': - specifier: ^0.0.14-alpha - version: 0.0.14-alpha(bufferutil@4.0.9)(react-native@0.78.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(typescript@5.8.2)(utf-8-validate@5.0.10) + specifier: ^0.0.16-alpha + version: 0.0.16-alpha(bufferutil@4.0.9)(react-native@0.78.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(typescript@5.8.2)(utf-8-validate@5.0.10) '@ipshipyard/node-datachannel': specifier: 0.26.5 version: 0.26.5 @@ -782,8 +782,8 @@ packages: '@dabh/diagnostics@2.0.3': resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} - '@debros/network@0.0.14-alpha': - resolution: {integrity: sha512-dMImNOU3f3PURLPqgY4UZrn7cpr4mGuPsDDrTe/HJZey1aa139R3BPJwCmq6EN/MjGUyL2liTjX+ZQ5qBqTbpg==} + '@debros/network@0.0.16-alpha': + resolution: {integrity: sha512-QYPUcoRF80tO8vD8cbPRjXIm17JW5Eul5au+XHyg2SXuWA9XRdIG0tc6Nk46rUll4lI0g4mFVuUSEUpMrZ7UWA==} peerDependencies: typescript: '>=5.0.0' @@ -5798,7 +5798,7 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@debros/network@0.0.14-alpha(bufferutil@4.0.9)(react-native@0.78.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(typescript@5.8.2)(utf-8-validate@5.0.10)': + '@debros/network@0.0.16-alpha(bufferutil@4.0.9)(react-native@0.78.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(typescript@5.8.2)(utf-8-validate@5.0.10)': dependencies: '@chainsafe/libp2p-gossipsub': 14.1.1 '@chainsafe/libp2p-noise': 16.1.0 diff --git a/src/cli.ts b/src/cli.ts index 50503e1..03c5f8b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,7 +3,6 @@ import { program } from "commander"; import { uploadCommand } from "./commands/upload.js"; import { deployCommand } from "./commands/deploy.js"; import { listCommand } from "./commands/list.js"; -import { initCommand } from "./commands/init.js"; import { configureIPNSCommand } from "./commands/configure-ipns.js"; import { domainCommand } from "./commands/domain.js"; import { versionsCommand } from "./commands/versions.js"; @@ -24,7 +23,6 @@ program uploadCommand(program); deployCommand(program); listCommand(program); -initCommand(program); configureIPNSCommand(program); domainCommand(program); versionsCommand(program); diff --git a/src/commands/upload.ts b/src/commands/upload.ts index ebe1412..4fb86c7 100644 --- a/src/commands/upload.ts +++ b/src/commands/upload.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import { Command } from 'commander'; import { buildDockerImage, saveDockerImage } from '../services/docker.js'; import { uploadToIPFS, publishNodeList } from '../services/ipfs.js'; -import { announceDeployment } from '../services/network.js'; +import { announceDeployment, checkAppNameAvailability } from '../services/network.js'; import { logger } from '../utils/logger.js'; import { config } from '../utils/config.js'; @@ -30,6 +30,14 @@ export function uploadCommand(program: Command) { const appName = options.name || path.basename(folderPath); const tag = options.tag || 'latest'; + // Check if the app name is available in the network + const isAppNameAvailable = await checkAppNameAvailability(appName); + if (!isAppNameAvailable) { + logger.error(`Application name "${appName}" is already taken in the DeBros network.`); + logger.info('Please choose a different name using the --name option.'); + process.exit(1); + } + // Get the subdomain from options or use the app name const subdomain = options.domain || appName; diff --git a/src/services/network.ts b/src/services/network.ts index fd2ee2a..32bd4d0 100644 --- a/src/services/network.ts +++ b/src/services/network.ts @@ -3,6 +3,9 @@ import { getConnectedPeers, initIpfs, stopIpfs, + initOrbitDB, + openDB, + getOrbitDB, } from "@debros/network"; import { logger, startSpinner, stopSpinner } from "../utils/logger.js"; import { config } from "../utils/config.js"; @@ -12,6 +15,123 @@ import { NodeInfo } from "./ipfs.js"; // It does not manage local node deployment, but rather allows the CLI // to discover, communicate with, and deploy to remote nodes in the network +interface AppData { + appName: string; + cid: string; + version: string; + timestamp: string; + deployed: boolean; + domain?: string; + status: 'running' | 'stopped' | 'unknown'; +} + +/** + * Get the application_data OrbitDB instance + */ +async function getApplicationDataDB() { + try { + // Initialize IPFS if it's not already running + await initIpfs(); + + // Initialize OrbitDB + await initOrbitDB(); + + // Open the application_data database (create if it doesn't exist) + // Feed type is suitable for append-only logs like application deployment records + const db = await openDB('application_data', 'feed'); + + return db; + } catch (error) { + logger.error(`Failed to initialize application_data database: ${ + error instanceof Error ? error.message : String(error) + }`); + throw error; + } +} + +/** + * Check if an application name is available in the network + */ +export async function checkAppNameAvailability(appName: string): Promise { + const spinner = startSpinner(`Checking if application name "${appName}" is available...`); + + try { + // Get the application_data database + const db = await getApplicationDataDB(); + + // Query the database for entries with this app name + const entries = await db.iterator({ limit: -1 }).collect(); + const existingApps = entries + .map((entry: any) => entry.value.appName) + .filter((name: string) => name === appName); + + const isAvailable = existingApps.length === 0; + + stopSpinner(`Application name "${appName}" is ${isAvailable ? 'available' : 'already taken'}`, true); + + // Clean up IPFS + await stopIpfs(); + + return isAvailable; + } catch (error) { + stopSpinner( + `Failed to check application name availability: ${ + error instanceof Error ? error.message : String(error) + }`, + false + ); + throw error; + } +} + +/** + * Store application data in OrbitDB + */ +export async function storeAppData(appData: AppData): Promise { + const spinner = startSpinner(`Storing application data for ${appData.appName}...`); + + try { + // Get the application_data database + const db = await getApplicationDataDB(); + + // Add the application data to the database + const hash = await db.add(appData); + logger.debug(`Stored app data with hash: ${hash}`); + + // Create a message to broadcast the app data update to peers + const message = { + type: "app-data-update", + appData, + timestamp: Date.now(), + }; + + // Get the libp2p instance + const libp2p = getLibp2p(); + if (libp2p) { + // Publish to the debros-app-data topic + await libp2p.services.pubsub.publish( + "debros-app-data", + new TextEncoder().encode(JSON.stringify(message)) + ); + } + + stopSpinner(`Application data for ${appData.appName} stored successfully`, true); + + // Clean up IPFS + await stopIpfs(); + + return true; + } catch (error) { + stopSpinner( + `Failed to store application data: ${ + error instanceof Error ? error.message : String(error) + }`, + false + ); + throw error; + } +} + /** * Get all currently connected peers */ @@ -119,6 +239,17 @@ export async function announceDeployment( "debros-deploy", new TextEncoder().encode(JSON.stringify(message)) ); + + // Update app data in OrbitDB + await storeAppData({ + appName, + cid, + version, + timestamp: new Date().toISOString(), + deployed: true, + domain: appDomain, + status: 'running' + }); stopSpinner(`Deployment announced to the network`, true); @@ -202,6 +333,53 @@ export async function listenForDeployments( } } +/** + * Get all applications and their data + */ +export async function getAllApps(): Promise { + const spinner = startSpinner("Fetching all applications..."); + + try { + // Get the application_data database + const db = await getApplicationDataDB(); + + // Query all entries from the database + const entries = await db.iterator({ limit: -1 }).collect(); + + // Process the entries to get the latest status for each app + const appMap = new Map(); + + // Process entries in reverse (newest first) to get the latest status + entries.reverse().forEach((entry: any) => { + const appData = entry.value as AppData; + + // If we haven't seen this app before, or if this is a newer entry, update our record + if (!appMap.has(appData.appName) || + new Date(appMap.get(appData.appName)!.timestamp) < new Date(appData.timestamp)) { + appMap.set(appData.appName, appData); + } + }); + + // Convert the map values to an array + const apps = Array.from(appMap.values()); + + stopSpinner(`Found ${apps.length} applications`, true); + + // Clean up IPFS + await stopIpfs(); + + return apps; + } catch (error) { + stopSpinner( + `Failed to fetch applications: ${ + error instanceof Error ? error.message : String(error) + }`, + false + ); + throw error; + } +} + /** * Get versions of an application */ @@ -216,41 +394,39 @@ export async function getAppVersions(appName: string): Promise< const spinner = startSpinner(`Fetching versions for ${appName}...`); try { - // Initialize IPFS if it's not already running - await initIpfs(); - - // Get the libp2p instance - const libp2p = getLibp2p(); - if (!libp2p) { - throw new Error("LibP2P is not initialized"); + // Get the application_data database + const db = await getApplicationDataDB(); + + // Query all entries from the database for this app + const entries = await db.iterator({ limit: -1 }).collect(); + + // Filter entries for this app + const appEntries = entries + .filter((entry: any) => entry.value.appName === appName) + .map((entry: any) => { + const data = entry.value as AppData; + return { + tag: data.version, + cid: data.cid, + timestamp: data.timestamp, + deployed: data.deployed, + }; + }); + + // Sort by timestamp, newest first + const versions = appEntries.sort((a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + ); + + // Mark only the latest version as deployed if any are deployed + const latestDeployedIndex = versions.findIndex(v => v.deployed); + if (latestDeployedIndex !== -1) { + // Set all versions to not deployed + versions.forEach(v => v.deployed = false); + // Set only the latest to deployed + versions[latestDeployedIndex].deployed = true; } - // Get application versions from network - // In a real implementation, this would query OrbitDB or another storage mechanism - - // Simulate fetching version info from network - // This would typically come from querying the network or local database - const versions = [ - { - tag: "latest", - cid: "QmVersion1", - timestamp: new Date().toISOString(), - deployed: true, - }, - { - tag: "v1.0.0", - cid: "QmVersion2", - timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // 1 day ago - deployed: false, - }, - { - tag: "v0.9.0", - cid: "QmVersion3", - timestamp: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // 1 week ago - deployed: false, - }, - ]; - stopSpinner(`Found ${versions.length} versions for ${appName}`, true); // Clean up IPFS @@ -354,6 +530,17 @@ export async function rollbackToVersion( "debros-deploy", new TextEncoder().encode(JSON.stringify(message)) ); + + // Update app data in OrbitDB + await storeAppData({ + appName, + cid: targetVersion.cid, + version: targetVersion.tag, + timestamp: new Date().toISOString(), + deployed: true, + domain: appDomain, + status: 'running' + }); stopSpinner(`Rolled back ${appName} to version ${targetVersion.tag}`, true); @@ -415,6 +602,18 @@ export async function stopApplication( "debros-app-actions", new TextEncoder().encode(JSON.stringify(message)) ); + + // Update app data in OrbitDB + const apps = await getAllApps(); + const appData = apps.find(app => app.appName === appName); + + if (appData) { + await storeAppData({ + ...appData, + status: 'stopped', + timestamp: new Date().toISOString() + }); + } stopSpinner( `Stop command for ${appName} has been sent to the network`, @@ -493,6 +692,21 @@ export async function startApplication( "debros-app-actions", new TextEncoder().encode(JSON.stringify(message)) ); + + // Update app data in OrbitDB + const apps = await getAllApps(); + const appData = apps.find(app => app.appName === appName); + const appDomain = appData?.domain || `${appName}.${config.defaultDomain}`; + + await storeAppData({ + appName, + cid: targetVersion.cid, + version: versionTag, + timestamp: new Date().toISOString(), + deployed: true, + domain: appDomain, + status: 'running' + }); stopSpinner( `Start command for ${appName} with version ${versionTag} has been sent to the network`, @@ -553,6 +767,15 @@ export async function deleteApplication( "debros-app-actions", new TextEncoder().encode(JSON.stringify(message)) ); + + // Update app data in OrbitDB - in a real implementation, we would remove the app data + const apps = await getAllApps(); + const appData = apps.find(app => app.appName === appName); + + if (appData) { + // In a real implementation, this would delete the app data from OrbitDB + logger.debug(`Deleting app data for ${appName}`); + } stopSpinner( `Delete command for ${appName} has been sent to the network`, @@ -609,4 +832,4 @@ export async function stopListeningForDeployments() { ); throw error; } -} +} \ No newline at end of file diff --git a/src/utils/config.ts b/src/utils/config.ts index 2422262..011a1b6 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -34,7 +34,7 @@ const defaultConfig: DebrosCliConfig = { ], defaultDomain: 'debros.sol', solanaEndpoint: 'https://api.mainnet-beta.solana.com', - defaultDeploymentStrategy: 'k3s' + defaultDeploymentStrategy: 'docker' }; // Ensure config directory exists