UNPKG

@zburn/core

Version:

Core utilities for the ZBURN ecosystem.

197 lines (168 loc) 6.25 kB
const fs = require('fs'); const { promises } = require('fs'); const fsPromises = promises; const path = require('path'); const { execSync, spawn } = require('child_process'); const { Blob } = require('buffer'); const { addToHistory, listHistory } = require('./history.js'); const { stimuleCidOnPublicGateways, pinEveryWhere } = require('./pin.js'); const kubo = require('kubo'); const kubo_path = kubo.path; const IPFS_REPO_PATH = process.env.IPFS_PATH || path.join(process.env.HOME || process.env.USERPROFILE, '.ipfs_zburn'); const IPFS_API_URL = "http://127.0.0.1:5001/api/v0"; /** * Recursively collect all files from a directory with their relative paths. */ function getAllFiles(dir, base = dir) { let files = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const fullPath = path.join(dir, entry.name); const relPath = path.relative(base, fullPath).replace(/\\/g, '/'); if (entry.isDirectory()) { files = files.concat(getAllFiles(fullPath, base)); } else if (entry.isFile()) { files.push({ fullPath, relPath }); } } return files; } /** * Generates an IPFS CID by adding a folder to a local IPFS node. * Works on Node.js 18+ and Node.js 22 (with native fetch + FormData + Blob). */ async function generateCID(folderPath) { const fetch = globalThis.fetch; const stats = fs.existsSync(folderPath) && fs.statSync(folderPath); if (!stats || !stats.isDirectory()) { throw new Error('Invalid folder path'); } const files = getAllFiles(folderPath); if (files.length === 0) { throw new Error('The folder is empty'); } const form = new FormData(); for (const file of files) { const content = fs.readFileSync(file.fullPath); // Read as buffer const blob = new Blob([content], { type: 'application/octet-stream' }); form.append('file', blob, file.relPath); // relPath = filename/path in IPFS } const ipfsApiUrl = IPFS_API_URL+'/add?recursive=true&wrap-with-directory=true'; console.log("Upload to local IPFS daemon...") let res; try { res = await fetch(ipfsApiUrl, { method: 'POST', body: form, }); } catch (err) { console.error(err); return null; } if (!res.ok) { const err = await res.text(); throw new Error(`IPFS upload failed: ${res.status} ${res.statusText}\n${err}`); } const responseText = await res.text(); const lines = responseText.trim().split('\n').map(line => JSON.parse(line)); const root = lines.find(line => line.Name === ''); if (!root || !root.Hash) { throw new Error('Could not determine root CID'); } console.log(`✅ Uploaded to local IPFS. Root CID: ${root.Hash}`); addToHistory({ cid: root.Hash, path: folderPath, date: new Date().toISOString() }); return root.Hash; } async function listLocalCIDs() { const history = listHistory(); console.log('Local IPFS CIDs:'); history.forEach(el => { console.log(`---------------------------------------------------\nCID: ${el.cid}\nPATH: ${el.path}\nDATE: ${el.date}\n`) }) return history; } async function startIPFSDaemon() { console.log("Starting IPFS daemon..."); const ipfsBinaryPath = kubo_path(); if (!ipfsBinaryPath) { throw new Error("Kubo (IPFS) binary not found. Please ensure 'npm install kubo' was successful."); } console.log(`Using IPFS binary from: ${ipfsBinaryPath}`); process.env.IPFS_PATH = IPFS_REPO_PATH; try { const repoExists = await fsPromises.stat(path.join(IPFS_REPO_PATH, 'config')) .then(() => true) .catch(() => false); if (!repoExists) { console.log(`IPFS repository not found at ${IPFS_REPO_PATH}. Initializing...`); execSync(`${ipfsBinaryPath} init`, { stdio: 'inherit' }); console.log("IPFS repository initialized."); } else { console.log(`IPFS repository found at ${IPFS_REPO_PATH}. Skipping initialization.`); } console.log("Launching IPFS daemon in background..."); const daemonProcess = spawn(ipfsBinaryPath, ['daemon', '--enable-gc'], { stdio: ['ignore', process.stdout, process.stderr], env: { ...process.env, IPFS_PATH: IPFS_REPO_PATH } }); console.log("Waiting for IPFS daemon to become ready..."); let attempts = 0; const maxAttempts = 30; // Wait up to 30 * 1 second = 30 seconds const pollInterval = 1000; // Poll every 1 second while (attempts < maxAttempts) { try { const response = await fetch(IPFS_API_URL + '/id', { method: 'POST' }); if (response.ok) { console.log("✅ IPFS daemon is now running and ready!"); return; } } catch (fetchError) { } await new Promise(resolve => setTimeout(resolve, pollInterval)); attempts++; } throw new Error(`IPFS daemon did not become ready after ${maxAttempts} attempts.`); } catch (error) { console.error("Failed to start IPFS daemon:", error); throw error; } } async function stopIPFSDaemon() { console.log("Attempting to stop IPFS daemon..."); try { const ipfsBinaryPath = kubo_path(); if (!ipfsBinaryPath) { console.warn("Kubo (IPFS) binary path not found. Cannot stop daemon gracefully."); return; } process.env.IPFS_PATH = IPFS_REPO_PATH; // Ensure IPFS_PATH is set for shutdown execSync(`${ipfsBinaryPath} shutdown`, { stdio: 'inherit' }); console.log("IPFS daemon stopped successfully."); } catch (error) { console.error("Failed to stop IPFS daemon:", error.message); } } async function deployIPFS(folderPath) { try { await startIPFSDaemon(); const cid = await generateCID(folderPath); if (!cid) throw new Error(`CID Generation failed, is IPFS daemon running on ${IPFS_API_URL} ?`); await stimuleCidOnPublicGateways(cid); await pinEveryWhere(cid); await stopIPFSDaemon(); return cid; } catch (err) { console.error(err); return null; } } module.exports = { deployIPFS, listLocalCIDs };