1import { create } from 'kubo-rpc-client' 2import { CID } from 'multiformats/cid' 3import fs from 'fs/promises' 6import * as _ from 'lodash-es' 14const getIpfsApiUrl = () => getEnvConf().ipfsApiUrl || (process.env.OCWEB === '1' && 'http://ipfs:5001') || hetzn1IpfsApiUrl 15const getIpfsGatewayUrl = () => getEnvConf().ipfsGatewayUrl || hetzn1IpfsGatewayUrl 17export const ipfsGatewayLink = (cid: string, gatewayUrl?: string) => `${gatewayUrl || getIpfsGatewayUrl()}/${cid}` 19const getFilesRecursive = async (dir: string, baseDir = dir): Promise<{fullPath: string, relativePath: string}[]> => { 20 const entries = await fs.readdir(dir, {withFileTypes: true}) 21 const results = await Promise.all(entries.map(async (entry) => { 22 const fullPath = path.join(dir, entry.name) 23 if (entry.isDirectory()) return getFilesRecursive(fullPath, baseDir) 24 const relativePath = path.relative(baseDir, fullPath) 25 return [{fullPath, relativePath}] 27 return _.flatten(results) 30export const ipfsUploadDir = async ({dirPath, apiUrl = getIpfsApiUrl()}: {dirPath: string, apiUrl?: string}) => { 31 const client = create({url: apiUrl}) 32 const logPath = path.join(os.tmpdir(), `ipfs-upload-${Date.now()}.log`) 33 const logLines: string[] = [] 34 const stats = {cached: 0, reuploaded: 0, new: 0} 38 const allFiles = await getFilesRecursive(dirPath) 39 const files = _.filter(allFiles, f => path.basename(f.fullPath) !== '.DS_Store') 41 const mfsRoot = `/ens-site-${Date.now()}` 43 const fileCids: {relativePath: string, cid: string}[] = [] 44 for (let index = 0; index < files.length; index++) { 45 const {fullPath, relativePath} = files[index] 46 const content = await fs.readFile(fullPath) 47 const contentHash = calcHash(content.toString('base64'), 'sha256') 49 const {cacheHit, value: cachedCid} = await runMemoTempfileWithHit({cacheKeyA: ['ipfsUploadDir', contentHash]}, async () => { 50 const result = await client.add(content, {pin: false, cidVersion: 1}) 51 return result.cid.toString() 54 let cid = cachedCid, status = 'new' 56 const cidObj = CID.parse(cachedCid) 57 const existsOnNode = await client.block.stat(cidObj).then(() => true).catch(() => false) 62 const result = await client.add(content, {pin: false, cidVersion: 1}) 63 cid = result.cid.toString() 64 status = 're-uploaded' 70 logLines.push(`[${index + 1}/${files.length}] ${relativePath} → ${cid} (${status})`) 71 fileCids.push({relativePath, cid}) 74 await fs.writeFile(logPath, logLines.join('\n')) 76 if (stats.cached) statParts.push(`${stats.cached} cached`) 77 if (stats.reuploaded) statParts.push(`${stats.reuploaded} re-uploaded`) 78 if (stats.new) statParts.push(`${stats.new} new`) 79 console.log(` ${statParts.join(', ')} (log: ${logPath})`) 81 for (const {relativePath, cid} of fileCids) { 82 const mfsPath = `${mfsRoot}/${relativePath}` 83 const mfsDir = path.dirname(mfsPath) 84 await client.files.mkdir(mfsDir, {parents: true}).catch(() => {}) 85 await client.files.cp(`/ipfs/${cid}`, mfsPath) 88 const stat = await client.files.stat(mfsRoot) 89 const rootCid = CID.parse(stat.cid.toString()).toV1().toString() 93 for await (const _ of client.pin.ls({paths: [rootCid]})) { pinned = true; break } 96 console.log(`Already pinned: ${rootCid}`) 98 await client.pin.add(rootCid) 99 console.log(`Pinned: ${rootCid}`) 101 try { for await (const _ of client.routing.provide(CID.parse(rootCid))) {} } catch {} // catch:userapproved 102 await client.files.rm(mfsRoot, {recursive: true}).catch(() => {}) 103 return {rootCid, fileCids}