@wttp/site
Version:
Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools
241 lines • 10.5 kB
JavaScript
;
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