pinme 1.1.2 → 1.1.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +458 -650
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -99,7 +99,7 @@ var require_main = __commonJS({
|
|
|
99
99
|
var fs6 = require("fs");
|
|
100
100
|
var path6 = require("path");
|
|
101
101
|
var os3 = require("os");
|
|
102
|
-
var
|
|
102
|
+
var crypto2 = require("crypto");
|
|
103
103
|
var packageJson = require_package();
|
|
104
104
|
var version2 = packageJson.version;
|
|
105
105
|
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
@@ -295,7 +295,7 @@ var require_main = __commonJS({
|
|
|
295
295
|
const authTag = ciphertext.subarray(-16);
|
|
296
296
|
ciphertext = ciphertext.subarray(12, -16);
|
|
297
297
|
try {
|
|
298
|
-
const aesgcm =
|
|
298
|
+
const aesgcm = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
|
|
299
299
|
aesgcm.setAuthTag(authTag);
|
|
300
300
|
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
301
301
|
} catch (error) {
|
|
@@ -1481,15 +1481,15 @@ var require_follow_redirects = __commonJS({
|
|
|
1481
1481
|
// bin/index.ts
|
|
1482
1482
|
var import_dotenv = __toESM(require_main());
|
|
1483
1483
|
var import_commander = require("commander");
|
|
1484
|
-
var
|
|
1484
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
1485
1485
|
var import_figlet3 = __toESM(require("figlet"));
|
|
1486
1486
|
|
|
1487
1487
|
// package.json
|
|
1488
|
-
var version = "1.1.2";
|
|
1488
|
+
var version = "1.1.3-alpha.2";
|
|
1489
1489
|
|
|
1490
1490
|
// bin/upload.ts
|
|
1491
1491
|
var import_path5 = __toESM(require("path"));
|
|
1492
|
-
var
|
|
1492
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
1493
1493
|
var import_inquirer = __toESM(require("inquirer"));
|
|
1494
1494
|
var import_figlet = __toESM(require("figlet"));
|
|
1495
1495
|
|
|
@@ -4360,12 +4360,12 @@ var {
|
|
|
4360
4360
|
mergeConfig: mergeConfig2
|
|
4361
4361
|
} = axios_default;
|
|
4362
4362
|
|
|
4363
|
-
// bin/utils/
|
|
4363
|
+
// bin/utils/uploadToIpfs2.ts
|
|
4364
4364
|
var import_fs_extra3 = __toESM(require("fs-extra"));
|
|
4365
4365
|
var import_path4 = __toESM(require("path"));
|
|
4366
4366
|
var import_form_data2 = __toESM(require("form-data"));
|
|
4367
4367
|
var import_ora = __toESM(require("ora"));
|
|
4368
|
-
var
|
|
4368
|
+
var crypto = __toESM(require("crypto"));
|
|
4369
4369
|
|
|
4370
4370
|
// bin/utils/uploadLimits.ts
|
|
4371
4371
|
var import_fs = __toESM(require("fs"));
|
|
@@ -4520,653 +4520,458 @@ function getDeviceId() {
|
|
|
4520
4520
|
return deviceId;
|
|
4521
4521
|
}
|
|
4522
4522
|
|
|
4523
|
-
// bin/utils/
|
|
4524
|
-
var
|
|
4525
|
-
var
|
|
4526
|
-
var
|
|
4527
|
-
var
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
if (code === 200) {
|
|
4547
|
-
consecutiveErrors = 0;
|
|
4548
|
-
stopProgressUpdates = false;
|
|
4549
|
-
if (data.is_ready) {
|
|
4550
|
-
smartProgress.complete(traceId);
|
|
4551
|
-
return data;
|
|
4552
|
-
} else {
|
|
4553
|
-
smartProgress.updateDisplay();
|
|
4554
|
-
}
|
|
4555
|
-
} else {
|
|
4556
|
-
console.log(import_chalk2.default.yellow(`Warning: ${msg}`));
|
|
4557
|
-
}
|
|
4558
|
-
} catch (error) {
|
|
4559
|
-
consecutiveErrors++;
|
|
4560
|
-
console.log(import_chalk2.default.yellow(`Polling error: ${error.message}`));
|
|
4561
|
-
if (stopProgressUpdates) {
|
|
4562
|
-
smartProgress.updateTimeOnly();
|
|
4563
|
-
}
|
|
4564
|
-
}
|
|
4565
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
4523
|
+
// bin/utils/uploadToIpfs2.ts
|
|
4524
|
+
var IPFS_API_URL = "https://up.pinme.dev/api/v3";
|
|
4525
|
+
var MAX_RETRIES = parseInt(process.env.MAX_RETRIES || "2");
|
|
4526
|
+
var RETRY_DELAY = parseInt(process.env.RETRY_DELAY_MS || "1000");
|
|
4527
|
+
var TIMEOUT = parseInt(process.env.TIMEOUT_MS || "600000");
|
|
4528
|
+
var MAX_POLL_TIME = parseInt("5") * 60 * 1e3;
|
|
4529
|
+
var POLL_INTERVAL = parseInt(process.env.POLL_INTERVAL_SECONDS || "2") * 1e3;
|
|
4530
|
+
var PROGRESS_UPDATE_INTERVAL = 200;
|
|
4531
|
+
var EXPECTED_UPLOAD_TIME = 6e4;
|
|
4532
|
+
var MAX_PROGRESS = 0.95;
|
|
4533
|
+
var StepProgressBar = class {
|
|
4534
|
+
spinner;
|
|
4535
|
+
fileName;
|
|
4536
|
+
startTime;
|
|
4537
|
+
currentStep = 0;
|
|
4538
|
+
stepStartTime = 0;
|
|
4539
|
+
progressInterval = null;
|
|
4540
|
+
constructor(fileName, isDirectory = false) {
|
|
4541
|
+
this.fileName = fileName;
|
|
4542
|
+
this.spinner = (0, import_ora.default)(`Preparing to upload ${fileName}...`).start();
|
|
4543
|
+
this.startTime = Date.now();
|
|
4544
|
+
this.stepStartTime = Date.now();
|
|
4545
|
+
this.startProgress();
|
|
4566
4546
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
traceId
|
|
4571
|
-
);
|
|
4572
|
-
return null;
|
|
4573
|
-
}
|
|
4574
|
-
function diagnoseDirectoryUploadError(directoryName, resData, expectedName) {
|
|
4575
|
-
const issues = [];
|
|
4576
|
-
if (directoryName.length > 100) {
|
|
4577
|
-
issues.push(
|
|
4578
|
-
`Directory name too long (${directoryName.length} characters, recommended under 100 characters)`
|
|
4579
|
-
);
|
|
4547
|
+
startStep(stepIndex, stepName) {
|
|
4548
|
+
this.currentStep = stepIndex;
|
|
4549
|
+
this.stepStartTime = Date.now();
|
|
4580
4550
|
}
|
|
4581
|
-
|
|
4582
|
-
issues.push(`Name returned by IPFS: ${availableName}`);
|
|
4583
|
-
issues.push(`Expected directory name: ${expectedName}`);
|
|
4584
|
-
const encodedName = encodeURIComponent(directoryName);
|
|
4585
|
-
if (encodedName !== directoryName) {
|
|
4586
|
-
issues.push(`Directory name after encoding: ${encodedName}`);
|
|
4551
|
+
updateProgress(progress, total) {
|
|
4587
4552
|
}
|
|
4588
|
-
|
|
4589
|
-
}
|
|
4590
|
-
function handleMultipartError(error, context) {
|
|
4591
|
-
if (error.message && error.message.includes("multipart: NextPart: EOF")) {
|
|
4592
|
-
return `Multipart form data error: ${context}. This usually indicates:
|
|
4593
|
-
- Empty directory or no valid files
|
|
4594
|
-
- File access permissions issue
|
|
4595
|
-
- Network interruption during upload
|
|
4596
|
-
- Server-side multipart parsing error`;
|
|
4597
|
-
}
|
|
4598
|
-
if (error.message && error.message.includes("ENOENT")) {
|
|
4599
|
-
return `File not found error: ${context}. Please check:
|
|
4600
|
-
- File path is correct
|
|
4601
|
-
- File exists and is accessible
|
|
4602
|
-
- No permission issues`;
|
|
4603
|
-
}
|
|
4604
|
-
if (error.message && error.message.includes("EACCES")) {
|
|
4605
|
-
return `Permission denied error: ${context}. Please check:
|
|
4606
|
-
- File read permissions
|
|
4607
|
-
- Directory access permissions
|
|
4608
|
-
- User has sufficient privileges`;
|
|
4609
|
-
}
|
|
4610
|
-
return `Upload error: ${error.message}`;
|
|
4611
|
-
}
|
|
4612
|
-
var ERROR_CODES = {
|
|
4613
|
-
"30001": `File too large, single file max size: ${"500"}MB,single folder max size: ${"500"}MB`,
|
|
4614
|
-
"30002": `Max storage quorum ${Number("1000") / 1e3} GB reached`
|
|
4615
|
-
};
|
|
4616
|
-
function loadFilesToArrRecursively(directoryPath, dist, basePath) {
|
|
4617
|
-
const filesArr = [];
|
|
4618
|
-
const sep = import_path4.default.sep;
|
|
4619
|
-
if (!basePath) {
|
|
4620
|
-
const parentDir = import_path4.default.dirname(directoryPath);
|
|
4621
|
-
basePath = parentDir.endsWith(sep) ? parentDir : parentDir + sep;
|
|
4622
|
-
}
|
|
4623
|
-
if (import_fs_extra3.default.statSync(directoryPath).isDirectory()) {
|
|
4624
|
-
const files = import_fs_extra3.default.readdirSync(directoryPath);
|
|
4625
|
-
files.forEach((file) => {
|
|
4626
|
-
const filePath = import_path4.default.join(directoryPath, file);
|
|
4627
|
-
if (import_fs_extra3.default.statSync(filePath).isFile()) {
|
|
4628
|
-
const sizeCheck = checkFileSizeLimit(filePath);
|
|
4629
|
-
if (sizeCheck.exceeds) {
|
|
4630
|
-
throw new Error(
|
|
4631
|
-
`File ${file} exceeds size limit of ${formatSize(
|
|
4632
|
-
sizeCheck.limit
|
|
4633
|
-
)} (size: ${formatSize(sizeCheck.size)})`
|
|
4634
|
-
);
|
|
4635
|
-
}
|
|
4636
|
-
const relativePath = filePath.replace(basePath, "");
|
|
4637
|
-
const encodedPath = relativePath.replaceAll(sep, "%2F");
|
|
4638
|
-
filesArr.push({
|
|
4639
|
-
name: encodedPath,
|
|
4640
|
-
path: filePath
|
|
4641
|
-
});
|
|
4642
|
-
} else if (import_fs_extra3.default.statSync(filePath).isDirectory()) {
|
|
4643
|
-
const recursiveFiles = loadFilesToArrRecursively(
|
|
4644
|
-
filePath,
|
|
4645
|
-
dist,
|
|
4646
|
-
basePath
|
|
4647
|
-
);
|
|
4648
|
-
filesArr.push(...recursiveFiles);
|
|
4649
|
-
}
|
|
4650
|
-
});
|
|
4651
|
-
} else {
|
|
4652
|
-
console.error("Error: path must be a directory");
|
|
4553
|
+
completeStep() {
|
|
4653
4554
|
}
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
let count = 0;
|
|
4658
|
-
const files = import_fs_extra3.default.readdirSync(directoryPath);
|
|
4659
|
-
for (const file of files) {
|
|
4660
|
-
const filePath = import_path4.default.join(directoryPath, file);
|
|
4661
|
-
const stats = import_fs_extra3.default.statSync(filePath);
|
|
4662
|
-
if (stats.isFile()) {
|
|
4663
|
-
count++;
|
|
4664
|
-
} else if (stats.isDirectory()) {
|
|
4665
|
-
count += countFilesInDirectory(filePath);
|
|
4666
|
-
}
|
|
4555
|
+
failStep(error) {
|
|
4556
|
+
this.stopProgress();
|
|
4557
|
+
this.spinner.fail(`Upload failed: ${error}`);
|
|
4667
4558
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
throw new Error(
|
|
4674
|
-
`Directory ${directoryPath} exceeds size limit of ${formatSize(
|
|
4675
|
-
sizeCheck.limit
|
|
4676
|
-
)} (size: ${formatSize(sizeCheck.size)})`
|
|
4677
|
-
);
|
|
4559
|
+
complete() {
|
|
4560
|
+
this.stopProgress();
|
|
4561
|
+
const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
4562
|
+
const progressBar = this.createProgressBar(1);
|
|
4563
|
+
this.spinner.succeed(`Upload completed ${progressBar} 100% (${totalTime}s)`);
|
|
4678
4564
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
const files = loadFilesToArrRecursively(directoryPath, dist);
|
|
4684
|
-
const totalFiles = files.length;
|
|
4685
|
-
if (totalFiles === 0) {
|
|
4686
|
-
throw new Error(
|
|
4687
|
-
`Directory ${directoryPath} is empty or contains no valid files`
|
|
4688
|
-
);
|
|
4565
|
+
fail(error) {
|
|
4566
|
+
this.stopProgress();
|
|
4567
|
+
const totalTime = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
4568
|
+
this.spinner.fail(`Upload failed: ${error} (${totalTime}s)`);
|
|
4689
4569
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4570
|
+
startProgress() {
|
|
4571
|
+
this.progressInterval = setInterval(() => {
|
|
4572
|
+
const elapsed = Date.now() - this.startTime;
|
|
4573
|
+
const progress = this.calculateProgress(elapsed);
|
|
4574
|
+
const progressBar = this.createProgressBar(progress);
|
|
4575
|
+
this.spinner.text = `Uploading ${this.fileName}... ${progressBar} ${Math.round(progress * 100)}%`;
|
|
4576
|
+
}, PROGRESS_UPDATE_INTERVAL);
|
|
4577
|
+
}
|
|
4578
|
+
stopProgress() {
|
|
4579
|
+
if (this.progressInterval) {
|
|
4580
|
+
clearInterval(this.progressInterval);
|
|
4581
|
+
this.progressInterval = null;
|
|
4697
4582
|
}
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4583
|
+
}
|
|
4584
|
+
calculateProgress(elapsed) {
|
|
4585
|
+
return Math.min(elapsed / EXPECTED_UPLOAD_TIME * MAX_PROGRESS, MAX_PROGRESS);
|
|
4586
|
+
}
|
|
4587
|
+
createProgressBar(progress, width = 20) {
|
|
4588
|
+
const percentage = Math.min(progress, 1);
|
|
4589
|
+
const filledWidth = Math.round(width * percentage);
|
|
4590
|
+
const emptyWidth = width - filledWidth;
|
|
4591
|
+
return `[${"\u2588".repeat(filledWidth)}${"\u2591".repeat(emptyWidth)}]`;
|
|
4592
|
+
}
|
|
4593
|
+
};
|
|
4594
|
+
async function calculateMD5(filePath) {
|
|
4595
|
+
return new Promise((resolve, reject) => {
|
|
4596
|
+
const hash = crypto.createHash("md5");
|
|
4597
|
+
const stream4 = import_fs_extra3.default.createReadStream(filePath);
|
|
4598
|
+
stream4.on("data", hash.update.bind(hash));
|
|
4599
|
+
stream4.on("end", () => resolve(hash.digest("hex")));
|
|
4600
|
+
stream4.on("error", reject);
|
|
4701
4601
|
});
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4602
|
+
}
|
|
4603
|
+
async function compressDirectory(sourcePath) {
|
|
4604
|
+
return new Promise((resolve, reject) => {
|
|
4605
|
+
const tempDir = import_path4.default.join(process.cwd(), "temp");
|
|
4606
|
+
if (!import_fs_extra3.default.existsSync(tempDir)) {
|
|
4607
|
+
import_fs_extra3.default.mkdirSync(tempDir, { recursive: true });
|
|
4608
|
+
}
|
|
4609
|
+
const outputPath = import_path4.default.join(
|
|
4610
|
+
tempDir,
|
|
4611
|
+
`${import_path4.default.basename(sourcePath)}_${Date.now()}.zip`
|
|
4612
|
+
);
|
|
4613
|
+
const output = import_fs_extra3.default.createWriteStream(outputPath);
|
|
4614
|
+
const zlib2 = require("zlib");
|
|
4615
|
+
const gzip = zlib2.createGzip({ level: 9 });
|
|
4616
|
+
output.on("close", () => resolve(outputPath));
|
|
4617
|
+
gzip.on("error", reject);
|
|
4618
|
+
gzip.pipe(output);
|
|
4619
|
+
const stats = import_fs_extra3.default.statSync(sourcePath);
|
|
4620
|
+
if (stats.isDirectory()) {
|
|
4621
|
+
const archive = require("archiver");
|
|
4622
|
+
const archiveStream = archive("zip", { zlib: { level: 9 } });
|
|
4623
|
+
archiveStream.on("error", reject);
|
|
4624
|
+
archiveStream.pipe(output);
|
|
4625
|
+
archiveStream.directory(sourcePath, false);
|
|
4626
|
+
archiveStream.finalize();
|
|
4627
|
+
} else {
|
|
4628
|
+
const fileStream = import_fs_extra3.default.createReadStream(sourcePath);
|
|
4629
|
+
fileStream.pipe(gzip);
|
|
4710
4630
|
}
|
|
4711
4631
|
});
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
);
|
|
4718
|
-
const timeInterval = setInterval(() => {
|
|
4719
|
-
smartProgress.updateTime();
|
|
4720
|
-
}, 1e3);
|
|
4721
|
-
const progressInterval = setInterval(() => {
|
|
4722
|
-
smartProgress.updateProgress();
|
|
4723
|
-
}, 200);
|
|
4632
|
+
}
|
|
4633
|
+
async function initChunkSession(filePath, deviceId, isDirectory = false) {
|
|
4634
|
+
const stats = import_fs_extra3.default.statSync(filePath);
|
|
4635
|
+
const fileName = import_path4.default.basename(filePath);
|
|
4636
|
+
const fileSize = stats.size;
|
|
4637
|
+
const md5 = await calculateMD5(filePath);
|
|
4724
4638
|
try {
|
|
4725
|
-
smartProgress.startUpload();
|
|
4726
4639
|
const response = await axios_default.post(
|
|
4727
|
-
`${
|
|
4728
|
-
formData,
|
|
4640
|
+
`${IPFS_API_URL}/chunk/init`,
|
|
4729
4641
|
{
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4642
|
+
file_name: fileName,
|
|
4643
|
+
file_size: fileSize,
|
|
4644
|
+
md5,
|
|
4645
|
+
is_directory: isDirectory,
|
|
4646
|
+
uid: deviceId
|
|
4647
|
+
},
|
|
4648
|
+
{
|
|
4649
|
+
timeout: TIMEOUT,
|
|
4650
|
+
headers: { "Content-Type": "application/json" }
|
|
4735
4651
|
}
|
|
4736
4652
|
);
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
if (!trace_id) {
|
|
4741
|
-
smartProgress.fail("No request id received from server");
|
|
4742
|
-
clearInterval(timeInterval);
|
|
4743
|
-
return null;
|
|
4744
|
-
}
|
|
4745
|
-
smartProgress.updateDisplay();
|
|
4746
|
-
const uploadResult = await pollUploadStatus(
|
|
4747
|
-
trace_id,
|
|
4748
|
-
deviceId,
|
|
4749
|
-
smartProgress,
|
|
4750
|
-
startTime
|
|
4751
|
-
);
|
|
4752
|
-
if (!uploadResult) {
|
|
4753
|
-
clearInterval(timeInterval);
|
|
4754
|
-
return null;
|
|
4755
|
-
}
|
|
4756
|
-
const directoryItem = uploadResult.upload_rst;
|
|
4757
|
-
if (directoryItem) {
|
|
4758
|
-
const fileCount = countFilesInDirectory(directoryPath);
|
|
4759
|
-
const uploadData = {
|
|
4760
|
-
path: directoryPath,
|
|
4761
|
-
filename: import_path4.default.basename(directoryPath),
|
|
4762
|
-
contentHash: directoryItem.Hash,
|
|
4763
|
-
previewHash: null,
|
|
4764
|
-
size: sizeCheck.size,
|
|
4765
|
-
fileCount,
|
|
4766
|
-
isDirectory: true,
|
|
4767
|
-
shortUrl: directoryItem.ShortUrl || null
|
|
4768
|
-
};
|
|
4769
|
-
saveUploadHistory(uploadData);
|
|
4770
|
-
clearInterval(timeInterval);
|
|
4771
|
-
return {
|
|
4772
|
-
hash: directoryItem.Hash,
|
|
4773
|
-
shortUrl: directoryItem.ShortUrl
|
|
4774
|
-
};
|
|
4653
|
+
const { code, msg, data } = response.data;
|
|
4654
|
+
if (code === 200 && data) {
|
|
4655
|
+
return data;
|
|
4775
4656
|
}
|
|
4776
|
-
|
|
4777
|
-
dist,
|
|
4778
|
-
uploadResult.upload_rst,
|
|
4779
|
-
dist
|
|
4780
|
-
);
|
|
4781
|
-
smartProgress.fail("Directory hash not found in response");
|
|
4782
|
-
console.log(
|
|
4783
|
-
import_chalk2.default.red(
|
|
4784
|
-
`
|
|
4785
|
-
\u274C Directory upload failed: Directory hash not found in response`
|
|
4786
|
-
)
|
|
4787
|
-
);
|
|
4788
|
-
console.log(import_chalk2.default.yellow(`
|
|
4789
|
-
\u{1F4CB} Error diagnosis information:`));
|
|
4790
|
-
console.log(import_chalk2.default.gray(` - ${diagnosticInfo}`));
|
|
4791
|
-
console.log(import_chalk2.default.blue(`
|
|
4792
|
-
\u{1F527} Solutions:`));
|
|
4793
|
-
console.log(
|
|
4794
|
-
import_chalk2.default.gray(` 1. Ensure directory is not empty and contains valid files`)
|
|
4795
|
-
);
|
|
4796
|
-
console.log(import_chalk2.default.gray(` 2. Check network connection stability`));
|
|
4797
|
-
console.log(
|
|
4798
|
-
import_chalk2.default.gray(` 3. Try uploading a smaller directory for testing`)
|
|
4799
|
-
);
|
|
4800
|
-
clearInterval(timeInterval);
|
|
4801
|
-
return null;
|
|
4657
|
+
throw new Error(`Session initialization failed: ${msg} (code: ${code})`);
|
|
4802
4658
|
} catch (error) {
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
if (error.message && error.message.includes("multipart")) {
|
|
4806
|
-
const errorMessage = handleMultipartError(
|
|
4807
|
-
error,
|
|
4808
|
-
`Directory upload: ${dist}`
|
|
4809
|
-
);
|
|
4810
|
-
smartProgress.fail(errorMessage);
|
|
4811
|
-
console.log(import_chalk2.default.red(`
|
|
4812
|
-
\u274C ${errorMessage}`));
|
|
4813
|
-
return null;
|
|
4814
|
-
}
|
|
4815
|
-
if (error.response && error.response.data && error.response.data.code) {
|
|
4816
|
-
const errorCode = error.response.data.code.toString();
|
|
4817
|
-
if (ERROR_CODES[errorCode]) {
|
|
4818
|
-
smartProgress.fail(
|
|
4819
|
-
`Error: ${ERROR_CODES[errorCode]} (Code: ${errorCode})`
|
|
4820
|
-
);
|
|
4821
|
-
console.log(
|
|
4822
|
-
import_chalk2.default.red(`Error: ${ERROR_CODES[errorCode]} (Code: ${errorCode})`)
|
|
4823
|
-
);
|
|
4824
|
-
return null;
|
|
4825
|
-
}
|
|
4659
|
+
if (axios_default.isAxiosError(error)) {
|
|
4660
|
+
throw new Error(`Network error: ${error.message}`);
|
|
4826
4661
|
}
|
|
4827
|
-
|
|
4828
|
-
console.log(import_chalk2.default.red(`Error: ${error.message}`));
|
|
4829
|
-
return null;
|
|
4662
|
+
throw error;
|
|
4830
4663
|
}
|
|
4831
4664
|
}
|
|
4832
|
-
async function
|
|
4833
|
-
const sizeCheck = checkFileSizeLimit(filePath);
|
|
4834
|
-
if (sizeCheck.exceeds) {
|
|
4835
|
-
throw new Error(
|
|
4836
|
-
`File ${filePath} exceeds size limit of ${formatSize(
|
|
4837
|
-
sizeCheck.limit
|
|
4838
|
-
)} (size: ${formatSize(sizeCheck.size)})`
|
|
4839
|
-
);
|
|
4840
|
-
}
|
|
4841
|
-
const fileName = filePath.split(import_path4.default.sep).pop() || "";
|
|
4842
|
-
if (!import_fs_extra3.default.existsSync(filePath)) {
|
|
4843
|
-
throw new Error(`File not found: ${filePath}`);
|
|
4844
|
-
}
|
|
4845
|
-
const fileStats = import_fs_extra3.default.statSync(filePath);
|
|
4846
|
-
if (!fileStats.isFile()) {
|
|
4847
|
-
throw new Error(`Path is not a file: ${filePath}`);
|
|
4848
|
-
}
|
|
4849
|
-
console.log(import_chalk2.default.blue("\n\u{1F4C4} File Upload Analysis:"));
|
|
4850
|
-
console.log(import_chalk2.default.gray(` File path: ${filePath}`));
|
|
4851
|
-
console.log(import_chalk2.default.gray(` File name: ${fileName}`));
|
|
4852
|
-
console.log(import_chalk2.default.gray(` File size: ${formatSize(fileStats.size)}`));
|
|
4853
|
-
console.log(import_chalk2.default.gray(` File exists: ${import_fs_extra3.default.existsSync(filePath)}`));
|
|
4854
|
-
console.log(import_chalk2.default.gray(` Is file: ${fileStats.isFile()}
|
|
4855
|
-
`));
|
|
4856
|
-
const startTime = Date.now();
|
|
4857
|
-
const spinner = (0, import_ora.default)(`Preparing upload...`).start();
|
|
4858
|
-
let totalSize = 0;
|
|
4859
|
-
try {
|
|
4860
|
-
const stats = import_fs_extra3.default.statSync(filePath);
|
|
4861
|
-
totalSize = stats.size;
|
|
4862
|
-
} catch (error) {
|
|
4863
|
-
}
|
|
4864
|
-
const smartProgress = new SmartProgressBar(fileName, 1, totalSize, spinner);
|
|
4865
|
-
const timeInterval = setInterval(() => {
|
|
4866
|
-
smartProgress.updateTime();
|
|
4867
|
-
}, 1e3);
|
|
4868
|
-
const progressInterval = setInterval(() => {
|
|
4869
|
-
smartProgress.updateProgress();
|
|
4870
|
-
}, 200);
|
|
4665
|
+
async function uploadChunkWithAbort(sessionId, chunkIndex, chunkData, deviceId, signal, retryCount = 0) {
|
|
4871
4666
|
try {
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4667
|
+
if (signal.aborted) {
|
|
4668
|
+
throw new Error("Request cancelled");
|
|
4669
|
+
}
|
|
4670
|
+
const form = new import_form_data2.default();
|
|
4671
|
+
form.append("session_id", sessionId);
|
|
4672
|
+
form.append("chunk_index", chunkIndex.toString());
|
|
4673
|
+
form.append("uid", deviceId);
|
|
4674
|
+
form.append("chunk", chunkData, {
|
|
4675
|
+
filename: `chunk_${chunkIndex}`,
|
|
4676
|
+
contentType: "application/octet-stream"
|
|
4877
4677
|
});
|
|
4878
4678
|
const response = await axios_default.post(
|
|
4879
|
-
`${
|
|
4880
|
-
|
|
4679
|
+
`${IPFS_API_URL}/chunk/upload`,
|
|
4680
|
+
form,
|
|
4881
4681
|
{
|
|
4882
|
-
headers: {
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
timeout: 18e5
|
|
4886
|
-
// 30 minutes timeout
|
|
4682
|
+
headers: { ...form.getHeaders() },
|
|
4683
|
+
timeout: TIMEOUT,
|
|
4684
|
+
signal
|
|
4887
4685
|
}
|
|
4888
4686
|
);
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
if (!trace_id) {
|
|
4893
|
-
smartProgress.fail("No request id received from server");
|
|
4894
|
-
clearInterval(timeInterval);
|
|
4895
|
-
return null;
|
|
4896
|
-
}
|
|
4897
|
-
smartProgress.updateDisplay();
|
|
4898
|
-
const uploadResult = await pollUploadStatus(
|
|
4899
|
-
trace_id,
|
|
4900
|
-
deviceId,
|
|
4901
|
-
smartProgress,
|
|
4902
|
-
startTime
|
|
4903
|
-
);
|
|
4904
|
-
if (!uploadResult) {
|
|
4905
|
-
clearInterval(timeInterval);
|
|
4906
|
-
return null;
|
|
4907
|
-
}
|
|
4908
|
-
const fileItem = uploadResult.upload_rst;
|
|
4909
|
-
if (fileItem) {
|
|
4910
|
-
const uploadData = {
|
|
4911
|
-
path: filePath,
|
|
4912
|
-
filename: fileName,
|
|
4913
|
-
contentHash: fileItem.Hash,
|
|
4914
|
-
previewHash: null,
|
|
4915
|
-
size: sizeCheck.size,
|
|
4916
|
-
fileCount: 1,
|
|
4917
|
-
isDirectory: false,
|
|
4918
|
-
shortUrl: fileItem.ShortUrl || null
|
|
4919
|
-
};
|
|
4920
|
-
saveUploadHistory(uploadData);
|
|
4921
|
-
smartProgress.complete(trace_id);
|
|
4922
|
-
clearInterval(timeInterval);
|
|
4923
|
-
return {
|
|
4924
|
-
hash: fileItem.Hash,
|
|
4925
|
-
shortUrl: fileItem.ShortUrl
|
|
4926
|
-
};
|
|
4687
|
+
const { code, msg, data } = response.data;
|
|
4688
|
+
if (code === 200 && data) {
|
|
4689
|
+
return data;
|
|
4927
4690
|
}
|
|
4928
|
-
|
|
4929
|
-
console.log(
|
|
4930
|
-
import_chalk2.default.red(`
|
|
4931
|
-
\u274C File upload failed: File hash not found in response`)
|
|
4932
|
-
);
|
|
4933
|
-
console.log(import_chalk2.default.yellow(`
|
|
4934
|
-
\u{1F4CB} Error diagnosis information:`));
|
|
4935
|
-
console.log(import_chalk2.default.gray(` - File name: ${fileName}`));
|
|
4936
|
-
console.log(
|
|
4937
|
-
import_chalk2.default.gray(` - Name returned by IPFS: ${uploadResult.upload_rst.Name}`)
|
|
4938
|
-
);
|
|
4939
|
-
console.log(import_chalk2.default.blue(`
|
|
4940
|
-
\u{1F527} Solutions:`));
|
|
4941
|
-
console.log(import_chalk2.default.gray(` 1. Check if file is corrupted or unreadable`));
|
|
4942
|
-
console.log(import_chalk2.default.gray(` 2. Check network connection stability`));
|
|
4943
|
-
console.log(import_chalk2.default.gray(` 3. Try uploading a smaller file for testing`));
|
|
4944
|
-
clearInterval(timeInterval);
|
|
4945
|
-
return null;
|
|
4691
|
+
throw new Error(`Chunk upload failed: ${msg} (code: ${code})`);
|
|
4946
4692
|
} catch (error) {
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4693
|
+
if (error.name === "CanceledError" || signal.aborted) {
|
|
4694
|
+
throw new Error("Request cancelled");
|
|
4695
|
+
}
|
|
4696
|
+
if (retryCount < MAX_RETRIES) {
|
|
4697
|
+
await delayWithAbortCheck(RETRY_DELAY, signal);
|
|
4698
|
+
return uploadChunkWithAbort(
|
|
4699
|
+
sessionId,
|
|
4700
|
+
chunkIndex,
|
|
4701
|
+
chunkData,
|
|
4702
|
+
deviceId,
|
|
4703
|
+
signal,
|
|
4704
|
+
retryCount + 1
|
|
4953
4705
|
);
|
|
4954
|
-
smartProgress.fail(errorMessage);
|
|
4955
|
-
console.log(import_chalk2.default.red(`
|
|
4956
|
-
\u274C ${errorMessage}`));
|
|
4957
|
-
return null;
|
|
4958
4706
|
}
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
smartProgress.fail(
|
|
4963
|
-
`Error: ${ERROR_CODES[errorCode]} (Code: ${errorCode})`
|
|
4964
|
-
);
|
|
4965
|
-
console.log(
|
|
4966
|
-
import_chalk2.default.red(`Error: ${ERROR_CODES[errorCode]} (Code: ${errorCode})`)
|
|
4967
|
-
);
|
|
4968
|
-
return null;
|
|
4969
|
-
}
|
|
4970
|
-
}
|
|
4971
|
-
smartProgress.fail(`Error: ${error.message}`);
|
|
4972
|
-
console.log(import_chalk2.default.red(`Error: ${error.message}`));
|
|
4973
|
-
return null;
|
|
4707
|
+
throw new Error(
|
|
4708
|
+
`Chunk ${chunkIndex + 1} upload failed after ${MAX_RETRIES} retries: ${error.message}`
|
|
4709
|
+
);
|
|
4974
4710
|
}
|
|
4975
4711
|
}
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
constructor(fileName, fileCount, totalSize, spinner) {
|
|
4989
|
-
this.fileName = fileName;
|
|
4990
|
-
this.fileCount = fileCount;
|
|
4991
|
-
this.totalSize = totalSize;
|
|
4992
|
-
this.spinner = spinner;
|
|
4993
|
-
this.timeConstant = this.calcTimeConstant(fileCount, totalSize);
|
|
4994
|
-
this.startTime = Date.now();
|
|
4995
|
-
this.uploadStartTime = 0;
|
|
4996
|
-
this.isUploading = false;
|
|
4997
|
-
this.isPolling = false;
|
|
4998
|
-
this.isCompleted = false;
|
|
4999
|
-
}
|
|
5000
|
-
// Calculate time constant based on file count and total size
|
|
5001
|
-
calcTimeConstant(fileCount, totalSize) {
|
|
5002
|
-
const base = 8e3;
|
|
5003
|
-
const countFactor = 0.3 * Math.log(1 + fileCount);
|
|
5004
|
-
const sizeFactor = 0.7 * Math.log(1 + totalSize / 1024 / 1024);
|
|
5005
|
-
const minTimeConstant = 15e3;
|
|
5006
|
-
const calculatedTimeConstant = base * (1 + countFactor + sizeFactor);
|
|
5007
|
-
return Math.max(calculatedTimeConstant, minTimeConstant);
|
|
5008
|
-
}
|
|
5009
|
-
// Calculate progress using exponential decay model with upper limit
|
|
5010
|
-
calculateProgress() {
|
|
5011
|
-
const elapsed = Date.now() - this.startTime;
|
|
5012
|
-
const rawProgress = 1 - Math.exp(-elapsed / this.timeConstant);
|
|
5013
|
-
const maxProgress = this.isPolling ? 0.95 : 0.9;
|
|
5014
|
-
return Math.min(rawProgress, maxProgress);
|
|
5015
|
-
}
|
|
5016
|
-
// Start upload phase
|
|
5017
|
-
startUpload() {
|
|
5018
|
-
this.isUploading = true;
|
|
5019
|
-
this.uploadStartTime = Date.now();
|
|
5020
|
-
}
|
|
5021
|
-
// Start polling phase
|
|
5022
|
-
startPolling() {
|
|
5023
|
-
this.isPolling = true;
|
|
5024
|
-
this.isUploading = false;
|
|
5025
|
-
}
|
|
5026
|
-
// Update progress display
|
|
5027
|
-
update() {
|
|
5028
|
-
if (this.isCompleted) {
|
|
4712
|
+
async function delayWithAbortCheck(delay, signal) {
|
|
4713
|
+
return new Promise((resolve, reject) => {
|
|
4714
|
+
const timeoutId = setTimeout(() => {
|
|
4715
|
+
if (signal.aborted) {
|
|
4716
|
+
reject(new Error("Request cancelled"));
|
|
4717
|
+
} else {
|
|
4718
|
+
resolve();
|
|
4719
|
+
}
|
|
4720
|
+
}, delay);
|
|
4721
|
+
if (signal.aborted) {
|
|
4722
|
+
clearTimeout(timeoutId);
|
|
4723
|
+
reject(new Error("Request cancelled"));
|
|
5029
4724
|
return;
|
|
5030
4725
|
}
|
|
5031
|
-
const
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
4726
|
+
const checkInterval = setInterval(() => {
|
|
4727
|
+
if (signal.aborted) {
|
|
4728
|
+
clearTimeout(timeoutId);
|
|
4729
|
+
clearInterval(checkInterval);
|
|
4730
|
+
reject(new Error("Request cancelled"));
|
|
4731
|
+
}
|
|
4732
|
+
}, 50);
|
|
4733
|
+
});
|
|
4734
|
+
}
|
|
4735
|
+
async function uploadFileChunks(filePath, sessionId, totalChunks, chunkSize, deviceId, progressBar) {
|
|
4736
|
+
const fileData = import_fs_extra3.default.readFileSync(filePath);
|
|
4737
|
+
const abortController = new AbortController();
|
|
4738
|
+
let completedCount = 0;
|
|
4739
|
+
let hasFatalError = false;
|
|
4740
|
+
let fatalError = null;
|
|
4741
|
+
const uploadTasks = Array.from({ length: totalChunks }, (_, chunkIndex) => {
|
|
4742
|
+
const start = chunkIndex * chunkSize;
|
|
4743
|
+
const end = Math.min(start + chunkSize, fileData.length);
|
|
4744
|
+
const chunkData = fileData.slice(start, end);
|
|
4745
|
+
return async () => {
|
|
4746
|
+
if (abortController.signal.aborted) return;
|
|
4747
|
+
try {
|
|
4748
|
+
await uploadChunkWithAbort(
|
|
4749
|
+
sessionId,
|
|
4750
|
+
chunkIndex,
|
|
4751
|
+
chunkData,
|
|
4752
|
+
deviceId,
|
|
4753
|
+
abortController.signal
|
|
4754
|
+
);
|
|
4755
|
+
if (abortController.signal.aborted) return;
|
|
4756
|
+
completedCount++;
|
|
4757
|
+
progressBar.updateProgress(completedCount, totalChunks);
|
|
4758
|
+
} catch (error) {
|
|
4759
|
+
if (error.name === "AbortError" || abortController.signal.aborted) {
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
hasFatalError = true;
|
|
4763
|
+
fatalError = `Chunk ${chunkIndex + 1}/${totalChunks} upload failed: ${error.message}`;
|
|
4764
|
+
abortController.abort();
|
|
4765
|
+
throw new Error(fatalError);
|
|
4766
|
+
}
|
|
4767
|
+
};
|
|
4768
|
+
});
|
|
4769
|
+
try {
|
|
4770
|
+
const results = await Promise.allSettled(uploadTasks.map((task) => task()));
|
|
4771
|
+
const failedResults = results.filter((result) => result.status === "rejected");
|
|
4772
|
+
if (failedResults.length > 0) {
|
|
4773
|
+
const firstFailure = failedResults[0];
|
|
4774
|
+
throw new Error(firstFailure.reason.message || "Error occurred during upload");
|
|
4775
|
+
}
|
|
4776
|
+
if (hasFatalError) {
|
|
4777
|
+
throw new Error(fatalError || "Unknown error occurred during upload");
|
|
5042
4778
|
}
|
|
5043
|
-
|
|
5044
|
-
|
|
4779
|
+
} catch (error) {
|
|
4780
|
+
throw fatalError ? new Error(fatalError) : error;
|
|
5045
4781
|
}
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
4782
|
+
}
|
|
4783
|
+
async function completeChunkUpload(sessionId, deviceId) {
|
|
4784
|
+
try {
|
|
4785
|
+
const response = await axios_default.post(
|
|
4786
|
+
`${IPFS_API_URL}/chunk/complete`,
|
|
4787
|
+
{ session_id: sessionId, uid: deviceId },
|
|
4788
|
+
{
|
|
4789
|
+
timeout: TIMEOUT,
|
|
4790
|
+
headers: { "Content-Type": "application/json" }
|
|
4791
|
+
}
|
|
4792
|
+
);
|
|
4793
|
+
const { code, msg, data } = response.data;
|
|
4794
|
+
if (code === 200 && data) {
|
|
4795
|
+
return data.trace_id;
|
|
5050
4796
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
let status = "";
|
|
5056
|
-
if (this.isUploading) {
|
|
5057
|
-
status = "uploading";
|
|
5058
|
-
} else if (this.isPolling) {
|
|
5059
|
-
status = "processing";
|
|
5060
|
-
} else {
|
|
5061
|
-
status = "preparing";
|
|
4797
|
+
throw new Error(`Complete upload failed: ${msg} (code: ${code})`);
|
|
4798
|
+
} catch (error) {
|
|
4799
|
+
if (axios_default.isAxiosError(error)) {
|
|
4800
|
+
throw new Error(`Network error: ${error.message}`);
|
|
5062
4801
|
}
|
|
5063
|
-
|
|
5064
|
-
this.spinner.text = `Uploading ${fileInfo} ${progressBar} ${duration} (${status})`;
|
|
4802
|
+
throw error;
|
|
5065
4803
|
}
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
4804
|
+
}
|
|
4805
|
+
async function getChunkStatus(sessionId, deviceId) {
|
|
4806
|
+
try {
|
|
4807
|
+
const response = await axios_default.get(
|
|
4808
|
+
`${IPFS_API_URL}/up_status`,
|
|
4809
|
+
{
|
|
4810
|
+
params: { trace_id: sessionId, uid: deviceId },
|
|
4811
|
+
timeout: TIMEOUT,
|
|
4812
|
+
headers: { "Content-Type": "application/json" }
|
|
4813
|
+
}
|
|
4814
|
+
);
|
|
4815
|
+
const { code, msg, data } = response.data;
|
|
4816
|
+
if (code === 200) {
|
|
4817
|
+
return data;
|
|
5070
4818
|
}
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
let status = "";
|
|
5076
|
-
if (this.isUploading) {
|
|
5077
|
-
status = "uploading";
|
|
5078
|
-
} else if (this.isPolling) {
|
|
5079
|
-
status = "processing";
|
|
5080
|
-
} else {
|
|
5081
|
-
status = "preparing";
|
|
4819
|
+
throw new Error(`Server returned error: ${msg} (code: ${code})`);
|
|
4820
|
+
} catch (error) {
|
|
4821
|
+
if (axios_default.isAxiosError(error)) {
|
|
4822
|
+
throw new Error(`Network error: ${error.message}`);
|
|
5082
4823
|
}
|
|
5083
|
-
|
|
5084
|
-
this.spinner.text = `Uploading ${fileInfo} ${progressBar} ${duration} (${status})`;
|
|
4824
|
+
throw error;
|
|
5085
4825
|
}
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
4826
|
+
}
|
|
4827
|
+
async function monitorChunkProgress(traceId, deviceId) {
|
|
4828
|
+
let consecutiveErrors = 0;
|
|
4829
|
+
const startTime = Date.now();
|
|
4830
|
+
while (Date.now() - startTime < MAX_POLL_TIME) {
|
|
4831
|
+
try {
|
|
4832
|
+
const status = await getChunkStatus(traceId, deviceId);
|
|
4833
|
+
consecutiveErrors = 0;
|
|
4834
|
+
if (status.is_ready && status.upload_rst.Hash) {
|
|
4835
|
+
return {
|
|
4836
|
+
hash: status.upload_rst.Hash,
|
|
4837
|
+
shortUrl: status.upload_rst.ShortUrl
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
} catch (error) {
|
|
4841
|
+
consecutiveErrors++;
|
|
4842
|
+
if (consecutiveErrors > 10) {
|
|
4843
|
+
throw new Error(`Polling failed: ${error.message}`);
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
5089
4847
|
}
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
4848
|
+
const maxPollTimeMinutes = Math.floor(MAX_POLL_TIME / (60 * 1e3));
|
|
4849
|
+
throw new Error(`Polling timeout after ${maxPollTimeMinutes} minutes`);
|
|
4850
|
+
}
|
|
4851
|
+
async function uploadDirectoryInChunks(directoryPath, deviceId) {
|
|
4852
|
+
const sizeCheck = checkDirectorySizeLimit(directoryPath);
|
|
4853
|
+
if (sizeCheck.exceeds) {
|
|
4854
|
+
throw new Error(
|
|
4855
|
+
`Directory ${directoryPath} exceeds size limit ${formatSize(sizeCheck.limit)} (size: ${formatSize(sizeCheck.size)})`
|
|
4856
|
+
);
|
|
5095
4857
|
}
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
4858
|
+
const progressBar = new StepProgressBar(import_path4.default.basename(directoryPath), true);
|
|
4859
|
+
try {
|
|
4860
|
+
progressBar.startStep(0, "Preparing compression");
|
|
4861
|
+
const compressedPath = await compressDirectory(directoryPath);
|
|
4862
|
+
progressBar.completeStep();
|
|
4863
|
+
progressBar.startStep(1, "Initializing session");
|
|
4864
|
+
const sessionInfo = await initChunkSession(compressedPath, deviceId, true);
|
|
4865
|
+
progressBar.completeStep();
|
|
4866
|
+
progressBar.startStep(2, "Chunk upload");
|
|
4867
|
+
await uploadFileChunks(
|
|
4868
|
+
compressedPath,
|
|
4869
|
+
sessionInfo.session_id,
|
|
4870
|
+
sessionInfo.total_chunks,
|
|
4871
|
+
sessionInfo.chunk_size,
|
|
4872
|
+
deviceId,
|
|
4873
|
+
progressBar
|
|
4874
|
+
);
|
|
4875
|
+
progressBar.completeStep();
|
|
4876
|
+
progressBar.startStep(3, "Completing upload");
|
|
4877
|
+
const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId);
|
|
4878
|
+
progressBar.completeStep();
|
|
4879
|
+
progressBar.startStep(4, "Waiting for processing");
|
|
4880
|
+
const result = await monitorChunkProgress(traceId, deviceId);
|
|
4881
|
+
progressBar.completeStep();
|
|
4882
|
+
try {
|
|
4883
|
+
import_fs_extra3.default.unlinkSync(compressedPath);
|
|
4884
|
+
} catch (error) {
|
|
5100
4885
|
}
|
|
5101
|
-
const
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
4886
|
+
const uploadData = {
|
|
4887
|
+
path: directoryPath,
|
|
4888
|
+
filename: import_path4.default.basename(directoryPath),
|
|
4889
|
+
contentHash: (result == null ? void 0 : result.hash) || "unknown",
|
|
4890
|
+
size: sizeCheck.size,
|
|
4891
|
+
fileCount: 0,
|
|
4892
|
+
isDirectory: true,
|
|
4893
|
+
shortUrl: (result == null ? void 0 : result.shortUrl) || null
|
|
4894
|
+
};
|
|
4895
|
+
saveUploadHistory(uploadData);
|
|
4896
|
+
if (!(result == null ? void 0 : result.hash)) {
|
|
4897
|
+
throw new Error("Server did not return valid hash value");
|
|
5111
4898
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
4899
|
+
progressBar.complete();
|
|
4900
|
+
return result;
|
|
4901
|
+
} catch (error) {
|
|
4902
|
+
progressBar.fail(error.message);
|
|
4903
|
+
throw error;
|
|
5116
4904
|
}
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
4905
|
+
}
|
|
4906
|
+
async function uploadFileInChunks(filePath, deviceId) {
|
|
4907
|
+
const sizeCheck = checkFileSizeLimit(filePath);
|
|
4908
|
+
if (sizeCheck.exceeds) {
|
|
4909
|
+
throw new Error(
|
|
4910
|
+
`File ${filePath} exceeds size limit ${formatSize(sizeCheck.limit)} (size: ${formatSize(sizeCheck.size)})`
|
|
4911
|
+
);
|
|
4912
|
+
}
|
|
4913
|
+
const fileName = import_path4.default.basename(filePath);
|
|
4914
|
+
const progressBar = new StepProgressBar(fileName, false);
|
|
4915
|
+
try {
|
|
4916
|
+
progressBar.startStep(0, "Initializing session");
|
|
4917
|
+
const sessionInfo = await initChunkSession(filePath, deviceId, false);
|
|
4918
|
+
progressBar.completeStep();
|
|
4919
|
+
progressBar.startStep(1, "Chunk upload");
|
|
4920
|
+
await uploadFileChunks(
|
|
4921
|
+
filePath,
|
|
4922
|
+
sessionInfo.session_id,
|
|
4923
|
+
sessionInfo.total_chunks,
|
|
4924
|
+
sessionInfo.chunk_size,
|
|
4925
|
+
deviceId,
|
|
4926
|
+
progressBar
|
|
4927
|
+
);
|
|
4928
|
+
progressBar.completeStep();
|
|
4929
|
+
progressBar.startStep(2, "Completing upload");
|
|
4930
|
+
const traceId = await completeChunkUpload(sessionInfo.session_id, deviceId);
|
|
4931
|
+
progressBar.completeStep();
|
|
4932
|
+
progressBar.startStep(3, "Waiting for processing");
|
|
4933
|
+
const result = await monitorChunkProgress(traceId, deviceId);
|
|
4934
|
+
progressBar.completeStep();
|
|
4935
|
+
const uploadData = {
|
|
4936
|
+
path: filePath,
|
|
4937
|
+
filename: fileName,
|
|
4938
|
+
contentHash: (result == null ? void 0 : result.hash) || "unknown",
|
|
4939
|
+
previewHash: null,
|
|
4940
|
+
size: sizeCheck.size,
|
|
4941
|
+
fileCount: 1,
|
|
4942
|
+
isDirectory: false,
|
|
4943
|
+
shortUrl: (result == null ? void 0 : result.shortUrl) || null
|
|
4944
|
+
};
|
|
4945
|
+
saveUploadHistory(uploadData);
|
|
4946
|
+
if (!(result == null ? void 0 : result.hash)) {
|
|
4947
|
+
throw new Error("Server did not return valid hash value");
|
|
5139
4948
|
}
|
|
4949
|
+
progressBar.complete();
|
|
4950
|
+
return result;
|
|
4951
|
+
} catch (error) {
|
|
4952
|
+
progressBar.fail(error.message);
|
|
4953
|
+
throw error;
|
|
5140
4954
|
}
|
|
5141
|
-
}
|
|
5142
|
-
async function
|
|
4955
|
+
}
|
|
4956
|
+
async function uploadToIpfs2_default(filePath) {
|
|
5143
4957
|
const deviceId = getDeviceId();
|
|
5144
4958
|
if (!deviceId) {
|
|
5145
4959
|
throw new Error("Device ID not found");
|
|
5146
4960
|
}
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
const result = await uploadFile(filePath, deviceId);
|
|
5157
|
-
if (result) {
|
|
5158
|
-
contentHash = result.hash;
|
|
5159
|
-
shortUrl = result.shortUrl || "";
|
|
4961
|
+
try {
|
|
4962
|
+
const isDirectory = import_fs_extra3.default.statSync(filePath).isDirectory();
|
|
4963
|
+
const result = isDirectory ? await uploadDirectoryInChunks(filePath, deviceId) : await uploadFileInChunks(filePath, deviceId);
|
|
4964
|
+
if (result == null ? void 0 : result.hash) {
|
|
4965
|
+
return {
|
|
4966
|
+
contentHash: result.hash,
|
|
4967
|
+
previewHash: null,
|
|
4968
|
+
shortUrl: result.shortUrl
|
|
4969
|
+
};
|
|
5160
4970
|
}
|
|
4971
|
+
return null;
|
|
4972
|
+
} catch (error) {
|
|
4973
|
+
return null;
|
|
5161
4974
|
}
|
|
5162
|
-
if (contentHash) {
|
|
5163
|
-
return {
|
|
5164
|
-
contentHash,
|
|
5165
|
-
previewHash: null,
|
|
5166
|
-
shortUrl
|
|
5167
|
-
};
|
|
5168
|
-
}
|
|
5169
|
-
return null;
|
|
5170
4975
|
}
|
|
5171
4976
|
|
|
5172
4977
|
// bin/upload.ts
|
|
@@ -5195,7 +5000,7 @@ function checkPathSync(inputPath) {
|
|
|
5195
5000
|
}
|
|
5196
5001
|
return null;
|
|
5197
5002
|
} catch (error) {
|
|
5198
|
-
console.error(
|
|
5003
|
+
console.error(import_chalk2.default.red(`error checking path: ${error.message}`));
|
|
5199
5004
|
return null;
|
|
5200
5005
|
}
|
|
5201
5006
|
}
|
|
@@ -5214,26 +5019,27 @@ var upload_default = async (options) => {
|
|
|
5214
5019
|
if (argPath && !argPath.startsWith("-")) {
|
|
5215
5020
|
const absolutePath = checkPathSync(argPath);
|
|
5216
5021
|
if (!absolutePath) {
|
|
5217
|
-
console.log(
|
|
5022
|
+
console.log(import_chalk2.default.red(`path ${argPath} does not exist`));
|
|
5218
5023
|
return;
|
|
5219
5024
|
}
|
|
5220
|
-
console.log(
|
|
5025
|
+
console.log(import_chalk2.default.blue(`uploading ${absolutePath} to ipfs...`));
|
|
5221
5026
|
try {
|
|
5222
|
-
const result = await
|
|
5027
|
+
const result = await uploadToIpfs2_default(absolutePath);
|
|
5223
5028
|
if (result) {
|
|
5224
5029
|
const encryptedCID = encryptHash(result.contentHash, secretKey);
|
|
5225
5030
|
console.log(
|
|
5226
|
-
|
|
5031
|
+
import_chalk2.default.cyan(
|
|
5227
5032
|
import_figlet.default.textSync("Successful", { horizontalLayout: "full" })
|
|
5228
5033
|
)
|
|
5229
5034
|
);
|
|
5230
|
-
console.log(
|
|
5231
|
-
console.log(
|
|
5035
|
+
console.log(import_chalk2.default.cyan(`URL:`));
|
|
5036
|
+
console.log(import_chalk2.default.cyan(`${URL2}${encryptedCID}`));
|
|
5037
|
+
console.log(import_chalk2.default.green("\n\u{1F389} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u7A0B\u5E8F\u9000\u51FA"));
|
|
5232
5038
|
}
|
|
5233
5039
|
} catch (error) {
|
|
5234
|
-
console.error(
|
|
5040
|
+
console.error(import_chalk2.default.red(`Error: ${error.message}`));
|
|
5235
5041
|
}
|
|
5236
|
-
|
|
5042
|
+
process.exit(0);
|
|
5237
5043
|
}
|
|
5238
5044
|
const answer = await import_inquirer.default.prompt([
|
|
5239
5045
|
{
|
|
@@ -5245,44 +5051,46 @@ var upload_default = async (options) => {
|
|
|
5245
5051
|
if (answer.path) {
|
|
5246
5052
|
const absolutePath = checkPathSync(answer.path);
|
|
5247
5053
|
if (!absolutePath) {
|
|
5248
|
-
console.log(
|
|
5054
|
+
console.log(import_chalk2.default.red(`path ${answer.path} does not exist`));
|
|
5249
5055
|
return;
|
|
5250
5056
|
}
|
|
5251
|
-
console.log(
|
|
5057
|
+
console.log(import_chalk2.default.blue(`uploading ${absolutePath} to ipfs...`));
|
|
5252
5058
|
try {
|
|
5253
|
-
const result = await
|
|
5059
|
+
const result = await uploadToIpfs2_default(absolutePath);
|
|
5254
5060
|
if (result) {
|
|
5255
5061
|
const encryptedCID = encryptHash(result.contentHash, secretKey);
|
|
5256
5062
|
console.log(
|
|
5257
|
-
|
|
5063
|
+
import_chalk2.default.cyan(
|
|
5258
5064
|
import_figlet.default.textSync("Successful", { horizontalLayout: "full" })
|
|
5259
5065
|
)
|
|
5260
5066
|
);
|
|
5261
|
-
console.log(
|
|
5262
|
-
console.log(
|
|
5067
|
+
console.log(import_chalk2.default.cyan(`URL:`));
|
|
5068
|
+
console.log(import_chalk2.default.cyan(`${URL2}${encryptedCID}`));
|
|
5069
|
+
console.log(import_chalk2.default.green("\n\u{1F389} \u4E0A\u4F20\u5B8C\u6210\uFF0C\u7A0B\u5E8F\u9000\u51FA"));
|
|
5263
5070
|
}
|
|
5264
5071
|
} catch (error) {
|
|
5265
|
-
console.error(
|
|
5072
|
+
console.error(import_chalk2.default.red(`Error: ${error.message}`));
|
|
5266
5073
|
}
|
|
5074
|
+
process.exit(0);
|
|
5267
5075
|
}
|
|
5268
5076
|
} catch (error) {
|
|
5269
|
-
console.error(
|
|
5077
|
+
console.error(import_chalk2.default.red(`error executing: ${error.message}`));
|
|
5270
5078
|
console.error(error.stack);
|
|
5271
5079
|
}
|
|
5272
5080
|
};
|
|
5273
5081
|
|
|
5274
5082
|
// bin/remove.ts
|
|
5275
|
-
var
|
|
5083
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
5276
5084
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
5277
5085
|
var import_figlet2 = __toESM(require("figlet"));
|
|
5278
5086
|
|
|
5279
5087
|
// bin/utils/removeFromIpfs.ts
|
|
5280
|
-
var
|
|
5281
|
-
var
|
|
5088
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
5089
|
+
var ipfsApiUrl = "https://up.pinme.dev/api/v3";
|
|
5282
5090
|
async function removeFromIpfs(value, type = "hash") {
|
|
5283
5091
|
try {
|
|
5284
5092
|
const uid = getDeviceId();
|
|
5285
|
-
console.log(
|
|
5093
|
+
console.log(import_chalk3.default.blue(`Removing content from IPFS: ${value}...`));
|
|
5286
5094
|
const queryParams = new URLSearchParams({
|
|
5287
5095
|
uid
|
|
5288
5096
|
});
|
|
@@ -5291,37 +5099,37 @@ async function removeFromIpfs(value, type = "hash") {
|
|
|
5291
5099
|
} else {
|
|
5292
5100
|
queryParams.append("arg", value);
|
|
5293
5101
|
}
|
|
5294
|
-
const response = await axios_default.post(`${
|
|
5102
|
+
const response = await axios_default.post(`${ipfsApiUrl}/block/rm?${queryParams.toString()}`, {
|
|
5295
5103
|
timeout: 3e4
|
|
5296
5104
|
// 30 seconds timeout
|
|
5297
5105
|
});
|
|
5298
5106
|
const { code, msg, data } = response.data;
|
|
5299
5107
|
if (code === 200) {
|
|
5300
|
-
console.log(
|
|
5301
|
-
console.log(
|
|
5108
|
+
console.log(import_chalk3.default.green("\u2713 Removal successful!"));
|
|
5109
|
+
console.log(import_chalk3.default.cyan(`Content ${type}: ${value} has been removed from IPFS network`));
|
|
5302
5110
|
return true;
|
|
5303
5111
|
} else {
|
|
5304
|
-
console.log(
|
|
5305
|
-
console.log(
|
|
5112
|
+
console.log(import_chalk3.default.red("\u2717 Removal failed"));
|
|
5113
|
+
console.log(import_chalk3.default.red(`Error: ${msg || "Unknown error occurred"}`));
|
|
5306
5114
|
return false;
|
|
5307
5115
|
}
|
|
5308
5116
|
} catch (error) {
|
|
5309
|
-
console.log(
|
|
5117
|
+
console.log(import_chalk3.default.red("\u2717 Removal failed", error));
|
|
5310
5118
|
if (error.response) {
|
|
5311
5119
|
const { status, data } = error.response;
|
|
5312
|
-
console.log(
|
|
5120
|
+
console.log(import_chalk3.default.red(`HTTP Error ${status}: ${(data == null ? void 0 : data.msg) || "Server error"}`));
|
|
5313
5121
|
if (status === 404) {
|
|
5314
|
-
console.log(
|
|
5122
|
+
console.log(import_chalk3.default.yellow("Content not found on the network or already removed"));
|
|
5315
5123
|
} else if (status === 403) {
|
|
5316
|
-
console.log(
|
|
5124
|
+
console.log(import_chalk3.default.yellow("Permission denied - you may not have access to remove this content"));
|
|
5317
5125
|
} else if (status === 500) {
|
|
5318
|
-
console.log(
|
|
5126
|
+
console.log(import_chalk3.default.yellow("Server internal error - please try again later"));
|
|
5319
5127
|
}
|
|
5320
5128
|
} else if (error.request) {
|
|
5321
|
-
console.log(
|
|
5322
|
-
console.log(
|
|
5129
|
+
console.log(import_chalk3.default.red("Network error: Unable to connect to IPFS service"));
|
|
5130
|
+
console.log(import_chalk3.default.yellow("Please check your internet connection and try again"));
|
|
5323
5131
|
} else {
|
|
5324
|
-
console.log(
|
|
5132
|
+
console.log(import_chalk3.default.red(`Error: ${error.message}`));
|
|
5325
5133
|
}
|
|
5326
5134
|
return false;
|
|
5327
5135
|
}
|
|
@@ -5380,30 +5188,30 @@ var remove_default = async (options) => {
|
|
|
5380
5188
|
if (argHash && !argHash.startsWith("-")) {
|
|
5381
5189
|
const parsedInput = parseInput(argHash);
|
|
5382
5190
|
if (!parsedInput) {
|
|
5383
|
-
console.log(
|
|
5384
|
-
console.log(
|
|
5385
|
-
console.log(
|
|
5386
|
-
console.log(
|
|
5387
|
-
console.log(
|
|
5388
|
-
console.log(
|
|
5191
|
+
console.log(import_chalk4.default.red(`Invalid input format: ${argHash}`));
|
|
5192
|
+
console.log(import_chalk4.default.yellow("Supported formats:"));
|
|
5193
|
+
console.log(import_chalk4.default.yellow(" - IPFS hash: bafybeig..."));
|
|
5194
|
+
console.log(import_chalk4.default.yellow(" - Full URL: https://bafybeig....pinme.dev"));
|
|
5195
|
+
console.log(import_chalk4.default.yellow(" - Subname: 3abt6ztu"));
|
|
5196
|
+
console.log(import_chalk4.default.yellow(" - Subname URL: https://3abt6ztu.pinit.eth.limo"));
|
|
5389
5197
|
return;
|
|
5390
5198
|
}
|
|
5391
5199
|
try {
|
|
5392
5200
|
const success = await removeFromIpfs(parsedInput.value, parsedInput.type);
|
|
5393
5201
|
if (success) {
|
|
5394
5202
|
console.log(
|
|
5395
|
-
|
|
5203
|
+
import_chalk4.default.cyan(
|
|
5396
5204
|
import_figlet2.default.textSync("Successful", { horizontalLayout: "full" })
|
|
5397
5205
|
)
|
|
5398
5206
|
);
|
|
5399
5207
|
}
|
|
5400
5208
|
} catch (error) {
|
|
5401
|
-
console.error(
|
|
5209
|
+
console.error(import_chalk4.default.red(`Error: ${error.message}`));
|
|
5402
5210
|
}
|
|
5403
5211
|
return;
|
|
5404
5212
|
}
|
|
5405
|
-
console.log(
|
|
5406
|
-
console.log(
|
|
5213
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Warning: This action will permanently remove the content from IPFS network"));
|
|
5214
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Make sure you have the correct IPFS hash"));
|
|
5407
5215
|
console.log("");
|
|
5408
5216
|
const confirmAnswer = await import_inquirer2.default.prompt([
|
|
5409
5217
|
{
|
|
@@ -5414,7 +5222,7 @@ var remove_default = async (options) => {
|
|
|
5414
5222
|
}
|
|
5415
5223
|
]);
|
|
5416
5224
|
if (!confirmAnswer.confirm) {
|
|
5417
|
-
console.log(
|
|
5225
|
+
console.log(import_chalk4.default.yellow("Operation cancelled"));
|
|
5418
5226
|
return;
|
|
5419
5227
|
}
|
|
5420
5228
|
const answer = await import_inquirer2.default.prompt([
|
|
@@ -5437,7 +5245,7 @@ var remove_default = async (options) => {
|
|
|
5437
5245
|
if (answer.input) {
|
|
5438
5246
|
const parsedInput = parseInput(answer.input.trim());
|
|
5439
5247
|
if (!parsedInput) {
|
|
5440
|
-
console.log(
|
|
5248
|
+
console.log(import_chalk4.default.red("Invalid input format"));
|
|
5441
5249
|
return;
|
|
5442
5250
|
}
|
|
5443
5251
|
const finalConfirm = await import_inquirer2.default.prompt([
|
|
@@ -5449,24 +5257,24 @@ var remove_default = async (options) => {
|
|
|
5449
5257
|
}
|
|
5450
5258
|
]);
|
|
5451
5259
|
if (!finalConfirm.confirm) {
|
|
5452
|
-
console.log(
|
|
5260
|
+
console.log(import_chalk4.default.yellow("Operation cancelled"));
|
|
5453
5261
|
return;
|
|
5454
5262
|
}
|
|
5455
5263
|
try {
|
|
5456
5264
|
const success = await removeFromIpfs(parsedInput.value, parsedInput.type);
|
|
5457
5265
|
if (success) {
|
|
5458
5266
|
console.log(
|
|
5459
|
-
|
|
5267
|
+
import_chalk4.default.cyan(
|
|
5460
5268
|
import_figlet2.default.textSync("Successful", { horizontalLayout: "full" })
|
|
5461
5269
|
)
|
|
5462
5270
|
);
|
|
5463
5271
|
}
|
|
5464
5272
|
} catch (error) {
|
|
5465
|
-
console.error(
|
|
5273
|
+
console.error(import_chalk4.default.red(`Error: ${error.message}`));
|
|
5466
5274
|
}
|
|
5467
5275
|
}
|
|
5468
5276
|
} catch (error) {
|
|
5469
|
-
console.error(
|
|
5277
|
+
console.error(import_chalk4.default.red(`Error executing remove command: ${error.message}`));
|
|
5470
5278
|
console.error(error.stack);
|
|
5471
5279
|
}
|
|
5472
5280
|
};
|
|
@@ -5475,11 +5283,11 @@ var remove_default = async (options) => {
|
|
|
5475
5283
|
import_dotenv.default.config();
|
|
5476
5284
|
function showBanner() {
|
|
5477
5285
|
console.log(
|
|
5478
|
-
|
|
5286
|
+
import_chalk5.default.cyan(
|
|
5479
5287
|
import_figlet3.default.textSync("Pinme", { horizontalLayout: "full" })
|
|
5480
5288
|
)
|
|
5481
5289
|
);
|
|
5482
|
-
console.log(
|
|
5290
|
+
console.log(import_chalk5.default.cyan("A command-line tool for uploading files to IPFS\n"));
|
|
5483
5291
|
}
|
|
5484
5292
|
var program = new import_commander.Command();
|
|
5485
5293
|
program.name("pinme").version(version).option("-v, --version", "output the current version");
|