@stoar/cli
Version:
CLI tool for STOAR - decentralized file storage on Arweave
131 lines • 5.03 kB
JavaScript
import { Command } from 'commander';
import { getWalletConfig, formatArweaveAmount } from '../utils/wallet.js';
import { output, success, createSpinner, formatBytes } from '../utils/output.js';
import { wrapCommand } from '../utils/error.js';
import { readFileSync, statSync } from 'node:fs';
import { basename } from 'node:path';
import { glob } from 'glob';
import { getMimeType } from '@stoar/sdk';
export const uploadCommand = new Command('upload')
.description('Upload files to Arweave')
.argument('<files...>', 'Files to upload (supports glob patterns)')
.option('-n, --name <name>', 'File name override')
.option('-t, --tags <tags>', 'Tags in key=value,key2=value2 format')
.option('-b, --batch', 'Upload multiple files as a batch')
.action(wrapCommand(async (files, options, command) => {
const globalOptions = command.parent?.opts() || {};
// Expand glob patterns
const expandedFiles = [];
for (const pattern of files) {
const matches = await glob(pattern);
if (matches.length === 0) {
throw new Error(`No files found matching pattern: ${pattern}`);
}
expandedFiles.push(...matches);
}
// Parse tags
const tags = {};
if (options.tags) {
const tagPairs = options.tags.split(',');
for (const pair of tagPairs) {
const [key, value] = pair.split('=');
if (key && value) {
tags[key] = value;
}
}
}
const { client } = await getWalletConfig(globalOptions);
// Check if we should use batch mode
const shouldBatch = options.batch || expandedFiles.length > 1;
if (shouldBatch) {
await uploadBatch(expandedFiles, client, tags, globalOptions);
}
else {
await uploadSingle(expandedFiles[0], client, tags, options.name, globalOptions);
}
}));
async function uploadSingle(filePath, client, tags, nameOverride, globalOptions) {
const spinner = createSpinner(`Uploading ${basename(filePath)}...`);
spinner.start();
try {
const fileData = readFileSync(filePath);
const stats = statSync(filePath);
const fileName = nameOverride || basename(filePath);
const result = await client.uploadFile(fileData, {
name: fileName,
size: stats.size,
contentType: getMimeType(filePath)
}, {
tags,
progress: (percentage) => {
spinner.text = `Uploading ${fileName}... ${Math.round(percentage)}%`;
}
});
spinner.stop();
const uploadData = {
transactionId: result.id,
url: `https://arweave.net/${result.id}`,
fileName,
size: formatBytes(stats.size),
cost: result.reward ? formatArweaveAmount(result.reward) : 'Pending confirmation'
};
output(uploadData, globalOptions);
if (!globalOptions?.json && !globalOptions?.quiet) {
success(`Uploaded: ${uploadData.url}`);
console.log(` Cost: ${uploadData.cost}`);
}
}
catch (err) {
spinner.stop();
throw err;
}
}
async function uploadBatch(filePaths, client, tags, globalOptions) {
const spinner = createSpinner(`Preparing batch upload of ${filePaths.length} files...`);
spinner.start();
try {
// Create batch
const batchId = client.createBatch();
let totalSize = 0;
const uploadResults = [];
// Add files to batch
for (const filePath of filePaths) {
const fileData = readFileSync(filePath);
const stats = statSync(filePath);
totalSize += stats.size;
const result = await client.uploadFile(fileData, {
name: basename(filePath),
size: stats.size,
contentType: getMimeType(filePath)
}, {
tags,
batch: batchId
});
uploadResults.push(result);
spinner.text = `Added ${basename(filePath)} to batch...`;
}
spinner.text = 'Committing batch...';
const batchStatus = await client.commitBatch(batchId);
spinner.stop();
const batchData = {
bundleId: batchStatus.bundleId,
url: `https://arweave.net/${batchStatus.bundleId}`,
fileCount: filePaths.length,
totalSize: formatBytes(totalSize),
cost: batchStatus.totalCost ? `${batchStatus.totalCost} AR` : 'N/A',
items: uploadResults
};
output(batchData, globalOptions);
if (!globalOptions?.json && !globalOptions?.quiet) {
success(`Batch uploaded: ${batchData.url}`);
console.log(` Files: ${batchData.fileCount}`);
console.log(` Total size: ${batchData.totalSize}`);
console.log(` Total cost: ${batchData.cost}`);
}
}
catch (err) {
spinner.stop();
throw err;
}
}
//# sourceMappingURL=upload.js.map