UNPKG

@wttp/site

Version:

Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools

241 lines 10.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.uploadDirectory = uploadDirectory; // Standalone version of uploadDirectory - works with standard ethers.js const ethers_1 = require("ethers"); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const core_1 = require("@wttp/core"); // Import artifact directly to avoid loading tasks const utils_1 = require("../utils"); const uploadFile_1 = require("./uploadFile"); const fetchResource_1 = require("./fetchResource"); const wttpIgnore_1 = require("../scripts/wttpIgnore"); const utils_2 = require("./utils"); const MAX_CHUNK_SIZE = 32 * 1024; // Helper function to check if a path is a directory function isDirectory(sourcePath) { return fs_1.default.statSync(sourcePath).isDirectory(); } // Helper function to get all files in a directory recursively with ignore filtering function getAllFiles(dirPath, wttpIgnore, arrayOfFiles = []) { const files = fs_1.default.readdirSync(dirPath); files.forEach((file) => { const fullPath = path_1.default.join(dirPath, file); // Check if this file/directory should be ignored if (wttpIgnore && wttpIgnore.shouldIgnore(fullPath)) { console.log(`🚫 Ignoring: ${path_1.default.relative(wttpIgnore.baseDir, fullPath)}`); return; } if (isDirectory(fullPath)) { arrayOfFiles = getAllFiles(fullPath, wttpIgnore, arrayOfFiles); } else { arrayOfFiles.push(fullPath); } }); return arrayOfFiles; } // Helper function to get all directories in a directory recursively with ignore filtering function getAllDirectories(dirPath, basePath, wttpIgnore, arrayOfDirs = []) { const files = fs_1.default.readdirSync(dirPath); const relativeDirPath = path_1.default.relative(basePath, dirPath); if (relativeDirPath) { arrayOfDirs.push(relativeDirPath); } files.forEach((file) => { const fullPath = path_1.default.join(dirPath, file); // Check if this directory should be ignored if (wttpIgnore && wttpIgnore.shouldIgnore(fullPath)) { console.log(`🚫 Ignoring directory: ${path_1.default.relative(wttpIgnore.baseDir, fullPath)}`); return; } if (isDirectory(fullPath)) { arrayOfDirs = getAllDirectories(fullPath, basePath, wttpIgnore, arrayOfDirs); } }); return arrayOfDirs; } // Helper function to find index files in a directory function findIndexFiles(dirPath) { const indexFiles = ["index.html", "index.htm"]; const foundIndexFiles = []; for (const indexFile of indexFiles) { const fullPath = path_1.default.join(dirPath, indexFile); if (fs_1.default.existsSync(fullPath) && fs_1.default.statSync(fullPath).isFile()) { foundIndexFiles.push(indexFile); } } return foundIndexFiles; } // Helper function to create directory metadata function createDirectoryMetadata(dirPath, basePath, wttpIgnore) { const directoryMetadata = {}; const files = fs_1.default.readdirSync(dirPath); files.forEach((file) => { const fullPath = path_1.default.join(dirPath, file); if (wttpIgnore && wttpIgnore.shouldIgnore(fullPath)) { return; } if (isDirectory(fullPath)) { directoryMetadata[file] = { type: "directory", path: path_1.default.relative(basePath, fullPath).replace(/\\/g, '/') }; } else { directoryMetadata[file] = { type: "file", path: path_1.default.relative(basePath, fullPath).replace(/\\/g, '/'), size: fs_1.default.statSync(fullPath).size }; } }); return { "directory": directoryMetadata }; } // Main upload directory function async function uploadDirectory( // wttpSite: Contract, wttpSiteAddress, sourcePath, destinationPath, options) { const { provider, signer, ignoreOptions, fileLimitBytes, gasLimitGwei, customPublisher } = options; const wttpSite = (0, utils_2.getWttpSite)(wttpSiteAddress, provider, signer); console.log(`🚀 Starting directory upload: ${sourcePath}${destinationPath}`); // Get currency symbol const currencySymbol = await (0, utils_1.getChainSymbol)(provider); // Track gas and royalties for summary let totalGasUsed = 0n; let totalRoyaltiesSpent = 0n; let totalFileSize = 0; // Parameter validation if (!fs_1.default.existsSync(sourcePath)) { throw new Error(`Source directory does not exist: ${sourcePath}`); } if (!isDirectory(sourcePath)) { throw new Error(`Source path ${sourcePath} is not a directory`); } destinationPath = (0, core_1.normalizePath)(destinationPath, true); // Initialize WTTP ignore system const wttpIgnore = (0, wttpIgnore_1.createWTTPIgnore)(sourcePath, ignoreOptions); console.log(`📋 Using ${wttpIgnore.getPatterns().length} ignore patterns`); // Find the index files for the directory const indexFiles = findIndexFiles(sourcePath); let indexLocation = `./${indexFiles[0]}`; // Defaults to index.html even if it doesn't exist // single index file let redirectCode = 301; let tempMetadataPath = null; if (indexFiles.length > 1) { // Multiple choices redirectCode = 300; // First, we need to create a json object with the directory metadata const directoryMetadata = createDirectoryMetadata(sourcePath, sourcePath, wttpIgnore); const directoryMetadataJson = JSON.stringify(directoryMetadata, null, 2); if (directoryMetadataJson.length < MAX_CHUNK_SIZE) { // the directory listing can fit in the location header indexLocation = directoryMetadataJson; } else { // Next, we need to create a temporary file with the directory metadata tempMetadataPath = path_1.default.join(process.cwd(), "temp_directory_metadata.json"); fs_1.default.writeFileSync(tempMetadataPath, directoryMetadataJson); // the directory listing is too large, so we need to upload it as a file await (0, uploadFile_1.uploadFile)(wttpSiteAddress, tempMetadataPath, destinationPath, { provider, signer, fileLimitBytes, gasLimitGwei, customPublisher }); } } // Check if the directory already has a header and if it's different from what we want to set const newHeaderData = { ...core_1.DEFAULT_HEADER, redirect: { code: redirectCode, location: indexLocation } }; let shouldDefine = true; try { const siteAddress = await wttpSite.getAddress(); const existingResource = await (0, fetchResource_1.fetchResource)(provider, siteAddress, destinationPath, { headRequest: true }); if (existingResource.response.head.status !== 404n) { // Resource exists, check if we need to update shouldDefine = false; // Simplified - in full version would compare headers } } catch (error) { // Resource doesn't exist, we need to define it shouldDefine = true; } // Define the directory if needed if (shouldDefine) { const requestHead = { path: destinationPath, ifModifiedSince: 0, ifNoneMatch: ethers_1.ethers.ZeroHash }; const defineRequest = { head: requestHead, data: newHeaderData }; console.log(`🚀 Sending DEFINE transaction with optimized gas settings...`); const gasSettings = await (0, utils_1.getDynamicGasSettings)(provider); // wttpSite is already created with signer via getWttpSite, so use it directly const defineTx = await wttpSite.DEFINE(defineRequest, gasSettings); // Wait for transaction to be fully confirmed const defineReceipt = await defineTx.wait(1); if (defineReceipt) { totalGasUsed += defineReceipt.gasUsed; } // Small delay to ensure nonce is updated in provider (critical for Hardhat automining) // Hardhat automining can cause nonce conflicts if transactions are sent too rapidly await new Promise(resolve => setTimeout(resolve, 200)); console.log(`Directory ${destinationPath} created successfully!`); } // Clean up the temporary file if (tempMetadataPath) { fs_1.default.unlinkSync(tempMetadataPath); } // Process all items in the directory const items = fs_1.default.readdirSync(sourcePath); for (const item of items) { const fullSourcePath = path_1.default.join(sourcePath, item); const fullDestPath = path_1.default.join(destinationPath, item).replace(/\\/g, '/'); // Check if this item should be ignored if (wttpIgnore.shouldIgnore(fullSourcePath)) { console.log(`🚫 Skipping ignored item: ${item}`); continue; } try { if (isDirectory(fullSourcePath)) { // Recursively upload subdirectory await uploadDirectory(wttpSiteAddress, fullSourcePath, fullDestPath, options); } else { // Upload file const fileSize = fs_1.default.statSync(fullSourcePath).size; totalFileSize += fileSize; await (0, uploadFile_1.uploadFile)(wttpSiteAddress, fullSourcePath, fullDestPath, { provider, signer, fileLimitBytes, gasLimitGwei, customPublisher }); } } catch (error) { console.error(`❌ Failed to upload ${item}:`, error); throw error; } } console.log(`🎉 Directory upload completed!`); console.log(`📊 Total file size: ${(totalFileSize / (1024 * 1024)).toFixed(2)} MB`); console.log(`⛽ Total gas used: ${totalGasUsed.toString()}`); console.log(`💰 Total royalties spent: ${ethers_1.ethers.formatEther(totalRoyaltiesSpent)} ${currencySymbol}`); } //# sourceMappingURL=uploadDirectory.js.map