@wttp/site
Version:
Web3 Transfer Protocol (WTTP) - Site Contracts and deployment tools
285 lines • 12.2 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;
const hardhat_1 = require("hardhat");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const core_1 = require("@wttp/core");
const uploadFile_1 = require("./uploadFile");
const fetchResource_1 = require("./fetchResource");
const wttpIgnore_1 = require("./wttpIgnore");
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 determine the index file for a directory
function findIndexFiles(dirPath) {
const files = fs_1.default.readdirSync(dirPath);
// Priority order for index files
const indexPriority = [
"index.html",
"index.htm",
"index.js",
"index.json",
"index.md",
"index.txt"
];
let indexFiles = [];
for (const indexFile of indexPriority) {
if (files.includes(indexFile)) {
indexFiles.push(indexFile);
}
}
if (indexFiles.length < 1) {
indexFiles.push("index.html");
}
return indexFiles;
}
// Helper function to create directory metadata with ignore filtering
function createDirectoryMetadata(dirPath, basePath, wttpIgnore) {
const files = fs_1.default.readdirSync(dirPath);
const directoryMetadata = {};
files.forEach((file) => {
const fullPath = path_1.default.join(dirPath, file);
// Check if this file/directory should be ignored
if (wttpIgnore && wttpIgnore.shouldIgnore(fullPath)) {
return; // Skip ignored files
}
if (isDirectory(fullPath)) {
directoryMetadata[file] = { "directory": true };
}
else {
const mimeType = (0, uploadFile_1.getMimeType)(fullPath);
directoryMetadata[file] = {
"mimeType": mimeType,
"charset": "utf-8",
"encoding": "identity",
"language": "en-US"
};
}
});
return { "directory": directoryMetadata };
}
// Main upload directory function with enhanced error handling and validation
async function uploadDirectory(wttpSite, sourcePath, destinationPath, ignoreOptions) {
console.log(`🚀 Starting directory upload: ${sourcePath} → ${destinationPath}`);
// 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
// can be done async in background
await (0, uploadFile_1.uploadFile)(wttpSite, tempMetadataPath, destinationPath);
}
}
// 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)(siteAddress, destinationPath, { headRequest: true });
if (existingResource.response.head.status !== 404n) {
// Resource exists, compare headers
// const existingHeaderData = existingResource.response.head.headerInfo;
// // Convert BigInt values to numbers for comparison
// const normalizedExisting = {
// cache: {
// immutableFlag: existingHeaderData.cache.immutableFlag,
// preset: Number(existingHeaderData.cache.preset),
// custom: existingHeaderData.cache.custom
// },
// cors: {
// methods: Number(existingHeaderData.cors.methods),
// origins: existingHeaderData.cors.origins,
// preset: Number(existingHeaderData.cors.preset),
// custom: existingHeaderData.cors.custom
// },
// redirect: {
// code: Number(existingHeaderData.redirect.code),
// location: existingHeaderData.redirect.location
// }
// };
// const normalizedNew = {
// cache: {
// immutableFlag: newHeaderData.cache.immutableFlag,
// preset: Number(newHeaderData.cache.preset),
// custom: newHeaderData.cache.custom
// },
// cors: {
// methods: Number(newHeaderData.cors.methods),
// origins: newHeaderData.cors.origins,
// preset: Number(newHeaderData.cors.preset),
// custom: newHeaderData.cors.custom
// },
// redirect: {
// code: Number(newHeaderData.redirect.code),
// location: newHeaderData.redirect.location
// }
// };
if ((0, uploadFile_1.looseEqual)(existingResource.response.head.headerInfo, newHeaderData)) {
console.log(`📋 Directory header at ${destinationPath} is already up to date, skipping DEFINE`);
shouldDefine = false;
}
else {
console.log(`📋 Directory header at ${destinationPath} is different, will update with DEFINE`);
}
}
else {
console.log(`📋 Directory ${destinationPath} does not exist, will create with DEFINE`);
}
}
catch (error) {
console.log(`📋 Could not check existing header for ${destinationPath}, proceeding with DEFINE: ${error}`);
}
if (shouldDefine) {
// Upload the directory metadata with redirect header
console.log("Uploading directory metadata with redirect header...");
// Get dynamic gas settings for optimized transaction speed
const gasSettings = await (0, uploadFile_1.getDynamicGasSettings)();
let requestHead = {
path: destinationPath,
ifModifiedSince: 0,
ifNoneMatch: hardhat_1.ethers.ZeroHash
};
const defineRequest = {
head: requestHead,
data: newHeaderData
};
console.log(`🚀 Sending DEFINE transaction with optimized gas settings...`);
const defineTx = await wttpSite.DEFINE(defineRequest, gasSettings);
await defineTx.wait();
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 handle subdirectories
await uploadDirectory(wttpSite, fullSourcePath, fullDestPath, ignoreOptions);
}
else {
// Upload files
console.log(`📤 Uploading file: ${item}`);
await (0, uploadFile_1.uploadFile)(wttpSite, fullSourcePath, fullDestPath);
console.log(`✅ File uploaded successfully: ${item}`);
}
}
catch (error) {
console.error(`❌ Failed to upload resource ${item}:`, error);
throw new Error(`Failed to upload resource ${item}: ${error}`);
}
}
console.log(`Directory ${sourcePath} uploaded successfully to ${destinationPath}`);
return true;
}
// Command-line interface
async function main() {
const args = process.argv.slice(2);
if (args.length < 3) {
console.error("Usage: npx hardhat run scripts/uploadDirectory.ts <site-address> <source-directory> <destination-path>");
process.exit(1);
}
const [siteAddress, sourcePath, destinationPath] = args;
// Connect to the WTTP site
const wttpSite = await hardhat_1.ethers.getContractAt("Web3Site", siteAddress);
// Upload the directory with default ignore options
await uploadDirectory(wttpSite, sourcePath, destinationPath);
}
// Only execute the script if it's being run directly
if (require.main === module) {
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
}
//# sourceMappingURL=uploadDirectory.js.map