@shelby-protocol/cli 0.0.13 → 0.0.14
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/bin/entry.js +784 -372
- package/package.json +8 -6
package/bin/entry.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// package.json
|
|
7
|
-
var version = "0.0.
|
|
7
|
+
var version = "0.0.14";
|
|
8
8
|
|
|
9
9
|
// src/commands/account.tsx
|
|
10
10
|
import readline from "readline";
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
|
|
69
69
|
// ../../packages/sdk/dist/chunk-I6NG5GNL.mjs
|
|
70
70
|
function sleep(ms) {
|
|
71
|
-
return new Promise((
|
|
71
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// ../../node_modules/.pnpm/tslib@2.8.1/node_modules/tslib/tslib.es6.mjs
|
|
@@ -3270,7 +3270,7 @@ var extras = {
|
|
|
3270
3270
|
gql["default"] = gql;
|
|
3271
3271
|
var lib_default = gql;
|
|
3272
3272
|
|
|
3273
|
-
// ../../packages/sdk/dist/chunk-
|
|
3273
|
+
// ../../packages/sdk/dist/chunk-WJKSPJSS.mjs
|
|
3274
3274
|
var GetBlobsDocument = lib_default`
|
|
3275
3275
|
query getBlobs($where: blobs_bool_exp, $orderBy: [blobs_order_by!], $limit: Int, $offset: Int) {
|
|
3276
3276
|
blobs(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
|
|
@@ -3306,6 +3306,24 @@ var GetBlobActivitiesDocument = lib_default`
|
|
|
3306
3306
|
}
|
|
3307
3307
|
}
|
|
3308
3308
|
`;
|
|
3309
|
+
var GetBlobsCountDocument = lib_default`
|
|
3310
|
+
query getBlobsCount($where: blobs_bool_exp) {
|
|
3311
|
+
blobs_aggregate(where: $where) {
|
|
3312
|
+
aggregate {
|
|
3313
|
+
count
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
`;
|
|
3318
|
+
var GetBlobActivitiesCountDocument = lib_default`
|
|
3319
|
+
query getBlobActivitiesCount($where: blob_activities_bool_exp) {
|
|
3320
|
+
blob_activities_aggregate(where: $where) {
|
|
3321
|
+
aggregate {
|
|
3322
|
+
count
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
`;
|
|
3309
3327
|
var defaultWrapper = (action, _operationName, _operationType, _variables) => action();
|
|
3310
3328
|
function getSdk(client, withWrapper = defaultWrapper) {
|
|
3311
3329
|
return {
|
|
@@ -3314,11 +3332,17 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
3314
3332
|
},
|
|
3315
3333
|
getBlobActivities(variables, requestHeaders, signal) {
|
|
3316
3334
|
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivities", "query", variables);
|
|
3335
|
+
},
|
|
3336
|
+
getBlobsCount(variables, requestHeaders, signal) {
|
|
3337
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobsCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobsCount", "query", variables);
|
|
3338
|
+
},
|
|
3339
|
+
getBlobActivitiesCount(variables, requestHeaders, signal) {
|
|
3340
|
+
return withWrapper((wrappedRequestHeaders) => client.request({ document: GetBlobActivitiesCountDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), "getBlobActivitiesCount", "query", variables);
|
|
3317
3341
|
}
|
|
3318
3342
|
};
|
|
3319
3343
|
}
|
|
3320
3344
|
|
|
3321
|
-
// ../../packages/sdk/dist/chunk-
|
|
3345
|
+
// ../../packages/sdk/dist/chunk-T6TVHFJO.mjs
|
|
3322
3346
|
import { Network as Network3 } from "@aptos-labs/ts-sdk";
|
|
3323
3347
|
|
|
3324
3348
|
// ../../node_modules/.pnpm/graphql-request@7.2.0_graphql@16.11.0/node_modules/graphql-request/build/legacy/classes/ClientError.js
|
|
@@ -3833,7 +3857,7 @@ var parseRequestArgs = (documentOrOptions, variables, requestHeaders) => {
|
|
|
3833
3857
|
};
|
|
3834
3858
|
};
|
|
3835
3859
|
|
|
3836
|
-
// ../../packages/sdk/dist/chunk-
|
|
3860
|
+
// ../../packages/sdk/dist/chunk-T6TVHFJO.mjs
|
|
3837
3861
|
function createShelbyIndexerClient(baseUrl, options) {
|
|
3838
3862
|
const graphqlClient = new GraphQLClient(baseUrl, options);
|
|
3839
3863
|
return getSdk(graphqlClient);
|
|
@@ -3866,7 +3890,7 @@ function getShelbyIndexerClient(config) {
|
|
|
3866
3890
|
});
|
|
3867
3891
|
}
|
|
3868
3892
|
|
|
3869
|
-
// ../../packages/sdk/dist/chunk-
|
|
3893
|
+
// ../../packages/sdk/dist/chunk-4JZO2D7T.mjs
|
|
3870
3894
|
import { Hex as Hex2 } from "@aptos-labs/ts-sdk";
|
|
3871
3895
|
async function* readInChunks(input, chunkSize) {
|
|
3872
3896
|
let idx = 0;
|
|
@@ -3951,6 +3975,10 @@ function buildRequestUrl(path6, baseUrl) {
|
|
|
3951
3975
|
const safePath = path6.replace(/^\/+/, "");
|
|
3952
3976
|
return new URL(safePath, safeBase);
|
|
3953
3977
|
}
|
|
3978
|
+
function getBlobNameSuffix(blobName) {
|
|
3979
|
+
const parts = blobName.split("/");
|
|
3980
|
+
return parts.slice(1).join("/") || "";
|
|
3981
|
+
}
|
|
3954
3982
|
|
|
3955
3983
|
// ../../packages/sdk/dist/chunk-ZPW742E7.mjs
|
|
3956
3984
|
var ERASURE_CODE_PARAMS = {
|
|
@@ -4138,7 +4166,7 @@ var BlobNameSchema = z.string().min(1, "Blob name path parameter cannot be empty
|
|
|
4138
4166
|
message: "Blob name cannot end with a slash"
|
|
4139
4167
|
});
|
|
4140
4168
|
|
|
4141
|
-
// ../../packages/sdk/dist/chunk-
|
|
4169
|
+
// ../../packages/sdk/dist/chunk-L7H6EKII.mjs
|
|
4142
4170
|
import { AccountAddress as AccountAddress3 } from "@aptos-labs/ts-sdk";
|
|
4143
4171
|
function encodeURIComponentKeepSlashes(str) {
|
|
4144
4172
|
return encodeURIComponent(str).replace(/%2F/g, "/");
|
|
@@ -4408,7 +4436,7 @@ var createBlobKey = (params) => {
|
|
|
4408
4436
|
return `@${AccountAddress4.from(params.account).toStringLongWithoutPrefix()}/${params.blobName}`;
|
|
4409
4437
|
};
|
|
4410
4438
|
|
|
4411
|
-
// ../../packages/sdk/dist/chunk-
|
|
4439
|
+
// ../../packages/sdk/dist/chunk-PCNLFNAT.mjs
|
|
4412
4440
|
import { z as z2 } from "zod";
|
|
4413
4441
|
var ChunksetCommitmentSchema = z2.object({
|
|
4414
4442
|
// Chunkset root (vector commitment of child chunks)
|
|
@@ -4530,7 +4558,7 @@ function validatePrePaddedChunkset(chunkset, expectedSize, chunksetIdx) {
|
|
|
4530
4558
|
return chunkset;
|
|
4531
4559
|
}
|
|
4532
4560
|
|
|
4533
|
-
// ../../packages/sdk/dist/chunk-
|
|
4561
|
+
// ../../packages/sdk/dist/chunk-Y7KTNPPR.mjs
|
|
4534
4562
|
import {
|
|
4535
4563
|
AccountAddress as AccountAddress5,
|
|
4536
4564
|
Aptos as Aptos2,
|
|
@@ -4626,6 +4654,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4626
4654
|
).toUint8Array(),
|
|
4627
4655
|
owner: AccountAddress5.fromString(metadata.owner),
|
|
4628
4656
|
name: params.name,
|
|
4657
|
+
blobNameSuffix: getBlobNameSuffix(params.name),
|
|
4629
4658
|
size: Number(metadata.blob_size),
|
|
4630
4659
|
encoding,
|
|
4631
4660
|
expirationMicros: metadata.expiration_micros,
|
|
@@ -4658,13 +4687,14 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4658
4687
|
* ```
|
|
4659
4688
|
*/
|
|
4660
4689
|
getAccountBlobs(params) {
|
|
4690
|
+
const { where, ...rest } = params;
|
|
4661
4691
|
return this.getBlobs({
|
|
4662
4692
|
where: {
|
|
4663
|
-
...
|
|
4693
|
+
...where ?? {},
|
|
4664
4694
|
owner: { _eq: AccountAddress5.from(params.account).toString() }
|
|
4665
4695
|
},
|
|
4666
|
-
pagination:
|
|
4667
|
-
orderBy:
|
|
4696
|
+
pagination: rest.pagination,
|
|
4697
|
+
orderBy: rest.orderBy
|
|
4668
4698
|
});
|
|
4669
4699
|
}
|
|
4670
4700
|
/**
|
|
@@ -4686,8 +4716,14 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4686
4716
|
async getBlobs(params = {}) {
|
|
4687
4717
|
const { limit, offset } = params.pagination ?? {};
|
|
4688
4718
|
const { orderBy, where } = params;
|
|
4719
|
+
const currentMicros = Date.now() * 1e3;
|
|
4720
|
+
const defaultActiveFilter = {
|
|
4721
|
+
expires_at: { _gte: currentMicros },
|
|
4722
|
+
is_deleted: { _eq: "0" }
|
|
4723
|
+
};
|
|
4724
|
+
const finalWhere = where !== void 0 ? { ...defaultActiveFilter, ...where } : defaultActiveFilter;
|
|
4689
4725
|
const { blobs } = await this.indexer.getBlobs({
|
|
4690
|
-
where,
|
|
4726
|
+
where: finalWhere,
|
|
4691
4727
|
limit,
|
|
4692
4728
|
offset,
|
|
4693
4729
|
orderBy
|
|
@@ -4696,6 +4732,7 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4696
4732
|
(blob) => ({
|
|
4697
4733
|
owner: AccountAddress5.from(blob.owner),
|
|
4698
4734
|
name: blob.blob_name,
|
|
4735
|
+
blobNameSuffix: getBlobNameSuffix(blob.blob_name),
|
|
4699
4736
|
blobMerkleRoot: Hex3.fromHexInput(blob.blob_commitment).toUint8Array(),
|
|
4700
4737
|
size: Number(blob.size),
|
|
4701
4738
|
// TODO: Add encoding when supported in NCI
|
|
@@ -4741,28 +4778,36 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4741
4778
|
);
|
|
4742
4779
|
}
|
|
4743
4780
|
/**
|
|
4744
|
-
* Retrieves the
|
|
4745
|
-
* the commitment, the storage provider location, and the status of the chunk (stored or pending).
|
|
4781
|
+
* Retrieves the total number of blobs from the blockchain.
|
|
4746
4782
|
*
|
|
4747
|
-
* @
|
|
4748
|
-
*
|
|
4749
|
-
* @param params.account - The account namespace the blob is stored in (e.g. "0x1")
|
|
4750
|
-
* @param params.name - The name of the blob (e.g. "foo/bar")
|
|
4751
|
-
* @returns The chunks that make up the blob.
|
|
4783
|
+
* @param params.where (optional) - The where clause to filter the blobs by.
|
|
4784
|
+
* @returns The total number of blobs.
|
|
4752
4785
|
*
|
|
4753
4786
|
* @example
|
|
4754
4787
|
* ```typescript
|
|
4755
|
-
*
|
|
4756
|
-
*
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4788
|
+
* const count = await client.getBlobsCount();
|
|
4789
|
+
* ```
|
|
4790
|
+
*/
|
|
4791
|
+
async getBlobsCount(params) {
|
|
4792
|
+
const { where } = params;
|
|
4793
|
+
const { blobs_aggregate } = await this.indexer.getBlobsCount({ where });
|
|
4794
|
+
return blobs_aggregate?.aggregate?.count ?? 0;
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Retrieves the total number of blob activities from the blockchain.
|
|
4798
|
+
*
|
|
4799
|
+
* @param params.where (optional) - The where clause to filter the blob activities by.
|
|
4800
|
+
* @returns The total number of blob activities.
|
|
4760
4801
|
*
|
|
4761
|
-
*
|
|
4802
|
+
* @example
|
|
4803
|
+
* ```typescript
|
|
4804
|
+
* const count = await client.getBlobActivitiesCount();
|
|
4762
4805
|
* ```
|
|
4763
4806
|
*/
|
|
4764
|
-
|
|
4765
|
-
|
|
4807
|
+
async getBlobActivitiesCount(params) {
|
|
4808
|
+
const { where } = params;
|
|
4809
|
+
const { blob_activities_aggregate } = await this.indexer.getBlobActivitiesCount({ where });
|
|
4810
|
+
return blob_activities_aggregate?.aggregate?.count ?? 0;
|
|
4766
4811
|
}
|
|
4767
4812
|
/**
|
|
4768
4813
|
* Registers a blob on the blockchain by writing its merkle root and metadata.
|
|
@@ -4889,9 +4934,26 @@ var ShelbyBlobClient = class _ShelbyBlobClient {
|
|
|
4889
4934
|
]
|
|
4890
4935
|
};
|
|
4891
4936
|
}
|
|
4937
|
+
/**
|
|
4938
|
+
* Creates a transaction payload to delete a blob on the blockchain.
|
|
4939
|
+
* This is a static helper method for constructing the Move function call payload.
|
|
4940
|
+
*
|
|
4941
|
+
* @param params.deployer - Optional deployer account address. Defaults to SHELBY_DEPLOYER.
|
|
4942
|
+
* @param params.blobNameSuffix - The blob name suffix (e.g. "bar.txt").
|
|
4943
|
+
*
|
|
4944
|
+
* @returns An Aptos transaction payload data object for the delete_blob Move function.
|
|
4945
|
+
*
|
|
4946
|
+
* @see https://github.com/shelby/shelby/blob/64e9d7b4f0005e586faeb1e4085c79159234b6b6/move/shelby_contract/sources/global_metadata.move#L616
|
|
4947
|
+
*/
|
|
4948
|
+
static createDeleteBlobPayload(params) {
|
|
4949
|
+
return {
|
|
4950
|
+
function: `${(params.deployer ?? SHELBY_DEPLOYER).toString()}::global_metadata::delete_blob`,
|
|
4951
|
+
functionArguments: [params.blobNameSuffix]
|
|
4952
|
+
};
|
|
4953
|
+
}
|
|
4892
4954
|
};
|
|
4893
4955
|
|
|
4894
|
-
// ../../packages/sdk/dist/chunk-
|
|
4956
|
+
// ../../packages/sdk/dist/chunk-TPGMXZRD.mjs
|
|
4895
4957
|
import {
|
|
4896
4958
|
Aptos as Aptos3
|
|
4897
4959
|
} from "@aptos-labs/ts-sdk";
|
|
@@ -5046,7 +5108,7 @@ var ShelbyClient = class {
|
|
|
5046
5108
|
}
|
|
5047
5109
|
};
|
|
5048
5110
|
|
|
5049
|
-
// ../../packages/sdk/dist/chunk-
|
|
5111
|
+
// ../../packages/sdk/dist/chunk-2DD6Q5CK.mjs
|
|
5050
5112
|
var ShelbyNodeClient = class extends ShelbyClient {
|
|
5051
5113
|
};
|
|
5052
5114
|
|
|
@@ -5154,6 +5216,7 @@ var DEFAULT_CHUNK_SIZE_BYTES2 = 2 * 1024 * 1024;
|
|
|
5154
5216
|
|
|
5155
5217
|
// src/commands/account.tsx
|
|
5156
5218
|
import chalk from "chalk";
|
|
5219
|
+
import { filesize } from "filesize";
|
|
5157
5220
|
import { render } from "ink";
|
|
5158
5221
|
import { z as z9 } from "zod";
|
|
5159
5222
|
|
|
@@ -6266,6 +6329,9 @@ function getErasureCodingProvider() {
|
|
|
6266
6329
|
|
|
6267
6330
|
// src/utils/paths.ts
|
|
6268
6331
|
import path2 from "path";
|
|
6332
|
+
function endsWithDirectorySeparator(filePath) {
|
|
6333
|
+
return filePath.endsWith("/") || filePath.endsWith("\\") || filePath.endsWith(path2.sep);
|
|
6334
|
+
}
|
|
6269
6335
|
function normBlobName(pathModule, inputDirectoryName, filename, blobPrefix) {
|
|
6270
6336
|
const blobNameWithoutInputDir = pathModule.relative(
|
|
6271
6337
|
inputDirectoryName,
|
|
@@ -7029,10 +7095,10 @@ function askQuestion(question) {
|
|
|
7029
7095
|
input: process.stdin,
|
|
7030
7096
|
output: process.stdout
|
|
7031
7097
|
});
|
|
7032
|
-
return new Promise((
|
|
7098
|
+
return new Promise((resolve3) => {
|
|
7033
7099
|
rl.question(question, (answer) => {
|
|
7034
7100
|
rl.close();
|
|
7035
|
-
|
|
7101
|
+
resolve3(answer.trim());
|
|
7036
7102
|
});
|
|
7037
7103
|
});
|
|
7038
7104
|
}
|
|
@@ -7235,25 +7301,33 @@ function accountCommand(program) {
|
|
|
7235
7301
|
});
|
|
7236
7302
|
const activeAccount = getCurrentAccount(config, accountName).account;
|
|
7237
7303
|
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
7238
|
-
console.log(
|
|
7239
|
-
|
|
7304
|
+
console.log(`\u{1F50D} Retrieving blobs for ${accountName}`);
|
|
7305
|
+
console.log(`\u{1F464} Address: ${activeAccount.accountAddress.toString()}`);
|
|
7306
|
+
const shelbyExplorerUrl = getShelbyAccountExplorerUrl(
|
|
7307
|
+
shelbyConfig.network,
|
|
7308
|
+
activeAccount.accountAddress.toString()
|
|
7240
7309
|
);
|
|
7310
|
+
console.log(`\u{1F5C2}\uFE0F Shelby Explorer: ${shelbyExplorerUrl}`);
|
|
7241
7311
|
const blobs = await shelbyClient.coordination.getBlobs({
|
|
7242
7312
|
where: {
|
|
7243
7313
|
owner: { _eq: activeAccount.accountAddress.toString() }
|
|
7244
7314
|
}
|
|
7245
7315
|
});
|
|
7246
7316
|
console.log(
|
|
7247
|
-
|
|
7317
|
+
`
|
|
7318
|
+
\u2705 Retrieved ${blobs.length} blob${blobs.length === 1 ? "" : "s"}`
|
|
7248
7319
|
);
|
|
7320
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
7249
7321
|
if (blobs.length === 0) {
|
|
7250
7322
|
console.log("No blobs found.");
|
|
7323
|
+
console.log("\u2728 Done!");
|
|
7251
7324
|
return;
|
|
7252
7325
|
}
|
|
7326
|
+
console.log("\u{1F4E6} Stored Blobs");
|
|
7253
7327
|
const { table, wrapCell } = createResponsiveTable({
|
|
7254
7328
|
columns: [
|
|
7255
7329
|
{ header: "Name", width: 30, flex: true },
|
|
7256
|
-
{ header: "Size
|
|
7330
|
+
{ header: "Size", width: 12 },
|
|
7257
7331
|
{ header: "Expires", width: 25 }
|
|
7258
7332
|
],
|
|
7259
7333
|
padding: 12
|
|
@@ -7262,14 +7336,20 @@ function accountCommand(program) {
|
|
|
7262
7336
|
const expirationDate = new Date(blob.expirationMicros / 1e3);
|
|
7263
7337
|
const blobNameMatch = blob.name.match(/@[^/]+\/(.+)/);
|
|
7264
7338
|
const displayName = blobNameMatch ? blobNameMatch[1] : blob.name;
|
|
7265
|
-
const
|
|
7266
|
-
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7339
|
+
const formattedSize = filesize(blob.size, { standard: "jedec" });
|
|
7340
|
+
const formattedDate = expirationDate.toLocaleDateString("en-US", {
|
|
7341
|
+
month: "short",
|
|
7342
|
+
day: "numeric",
|
|
7343
|
+
year: "numeric",
|
|
7344
|
+
hour: "numeric",
|
|
7345
|
+
minute: "2-digit",
|
|
7346
|
+
hour12: true
|
|
7347
|
+
});
|
|
7348
|
+
const row = [displayName, formattedSize, formattedDate];
|
|
7270
7349
|
table.push(row.map((cell, i) => wrapCell(cell, i)));
|
|
7271
7350
|
}
|
|
7272
7351
|
console.log(table.toString());
|
|
7352
|
+
console.log("\u2728 Done!");
|
|
7273
7353
|
} catch (error) {
|
|
7274
7354
|
let displayMessage;
|
|
7275
7355
|
if (error instanceof Error && error.message.includes("GraphQL Error (Code: 401)")) {
|
|
@@ -7776,52 +7856,99 @@ import { pipeline } from "stream/promises";
|
|
|
7776
7856
|
import ora from "ora";
|
|
7777
7857
|
import { z as z13 } from "zod";
|
|
7778
7858
|
var denormBlobName2 = (a, b, c) => denormBlobName(path3, a, b, c);
|
|
7779
|
-
var endsWithDirectorySeparator = (filePath) => {
|
|
7780
|
-
return filePath.endsWith("/") || filePath.endsWith("\\") || filePath.endsWith(path3.sep);
|
|
7781
|
-
};
|
|
7782
7859
|
var DownloadOptionsSchema = z13.object({
|
|
7783
|
-
|
|
7784
|
-
|
|
7860
|
+
source: z13.string({
|
|
7861
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing source blob name or prefix (first argument)\n\n\u{1F4A1} Usage:\n shelby download <source-blob-name> <destination-path> [options]\n\n\u{1F4DD} Examples:\n shelby download my-blob.txt ./myfile.txt\n shelby download my-folder/ ./my-folder/ -r\n"
|
|
7862
|
+
}).min(1, "Source blob name or directory prefix is required").describe("Blob name or directory prefix to download"),
|
|
7863
|
+
destination: z13.string({
|
|
7864
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing destination path (second argument)\n\n\u{1F4A1} Usage:\n shelby download <source-blob-name> <destination-path> [options]\n\n\u{1F4DD} Example:\n shelby download my-blob.txt ./myfile.txt\n"
|
|
7865
|
+
}).min(1, "`destination` must be a valid filepath").describe("Local path where to save the downloaded content"),
|
|
7785
7866
|
recursive: z13.boolean().default(false).describe("Download assuming canonical directory layout and recurse"),
|
|
7786
|
-
force: z13.boolean().default(false).describe("Overwrite the
|
|
7867
|
+
force: z13.boolean().default(false).describe("Overwrite the destination if it already exists")
|
|
7787
7868
|
}).refine(
|
|
7788
7869
|
(data) => {
|
|
7789
7870
|
if (data.recursive) {
|
|
7790
|
-
if (!data.
|
|
7871
|
+
if (!data.source.endsWith("/") || !endsWithDirectorySeparator(data.destination))
|
|
7791
7872
|
return false;
|
|
7792
7873
|
return true;
|
|
7793
7874
|
}
|
|
7794
7875
|
return true;
|
|
7795
7876
|
},
|
|
7796
|
-
{
|
|
7797
|
-
message:
|
|
7798
|
-
|
|
7877
|
+
(data) => ({
|
|
7878
|
+
message: `
|
|
7879
|
+
\u274C Invalid Download Options
|
|
7880
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7881
|
+
|
|
7882
|
+
\u26A0\uFE0F Recursive downloads require:
|
|
7883
|
+
- The source prefix must end with '/'
|
|
7884
|
+
- The destination path must end with '/' or '\\'
|
|
7885
|
+
|
|
7886
|
+
\u{1F9FE} Received:
|
|
7887
|
+
Source: ${data.source}
|
|
7888
|
+
Destination: ${data.destination}
|
|
7889
|
+
|
|
7890
|
+
\u{1F4A1} Tip: Add trailing slashes to both paths:
|
|
7891
|
+
\u{1F449} shelby download --recursive ${data.source.endsWith("/") ? data.source : `${data.source}/`} ${endsWithDirectorySeparator(data.destination) ? data.destination : `${data.destination}/`}
|
|
7892
|
+
`
|
|
7893
|
+
})
|
|
7799
7894
|
).refine(
|
|
7800
7895
|
(data) => {
|
|
7801
7896
|
if (!data.recursive) {
|
|
7802
|
-
if (!BlobNameSchema.safeParse(data.
|
|
7803
|
-
if (endsWithDirectorySeparator(data.
|
|
7897
|
+
if (!BlobNameSchema.safeParse(data.source).success) return false;
|
|
7898
|
+
if (endsWithDirectorySeparator(data.destination)) return false;
|
|
7804
7899
|
return true;
|
|
7805
7900
|
}
|
|
7806
7901
|
return true;
|
|
7807
7902
|
},
|
|
7808
|
-
{
|
|
7809
|
-
|
|
7903
|
+
(data) => {
|
|
7904
|
+
const sourceEndsWithSlash = data.source.endsWith("/") || data.source.endsWith("\\");
|
|
7905
|
+
const destEndsWithSlash = endsWithDirectorySeparator(data.destination);
|
|
7906
|
+
let tipMessage = "";
|
|
7907
|
+
if (sourceEndsWithSlash && destEndsWithSlash) {
|
|
7908
|
+
tipMessage = `\u{1F4A1} Tip: For downloading a directory, use the --recursive flag:
|
|
7909
|
+
\u{1F449} shelby download --recursive ${data.source} ${data.destination}`;
|
|
7910
|
+
} else if (sourceEndsWithSlash && !destEndsWithSlash) {
|
|
7911
|
+
tipMessage = `\u{1F4A1} Tip: For downloading a directory, both paths need trailing slashes:
|
|
7912
|
+
\u{1F449} shelby download --recursive ${data.source} ${data.destination}/`;
|
|
7913
|
+
} else if (!sourceEndsWithSlash && destEndsWithSlash) {
|
|
7914
|
+
tipMessage = `\u{1F4A1} Choose one of the following:
|
|
7915
|
+
|
|
7916
|
+
To download a single blob (remove trailing slash from destination):
|
|
7917
|
+
\u{1F449} shelby download ${data.source} ${data.destination.replace(/[\\/]+$/, "")}
|
|
7918
|
+
|
|
7919
|
+
To download a directory recursively (add trailing slash to source):
|
|
7920
|
+
\u{1F449} shelby download --recursive ${data.source}/ ${data.destination}`;
|
|
7921
|
+
}
|
|
7922
|
+
return {
|
|
7923
|
+
message: `
|
|
7924
|
+
\u274C Invalid Download Options
|
|
7925
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7926
|
+
|
|
7927
|
+
\u26A0\uFE0F Blob downloads (non-recursive) require:
|
|
7928
|
+
- The source blob name must NOT end with '/' or '\\'
|
|
7929
|
+
- The destination path must NOT end with '/' or '\\'
|
|
7930
|
+
|
|
7931
|
+
\u{1F9FE} Received:
|
|
7932
|
+
Source: ${data.source}
|
|
7933
|
+
Destination: ${data.destination}
|
|
7934
|
+
|
|
7935
|
+
` + tipMessage + "\n"
|
|
7936
|
+
};
|
|
7810
7937
|
}
|
|
7811
7938
|
);
|
|
7812
7939
|
async function validateOutput(options) {
|
|
7813
|
-
const parentDir = path3.dirname(options.
|
|
7940
|
+
const parentDir = path3.dirname(options.destination);
|
|
7814
7941
|
try {
|
|
7815
7942
|
const parentStats = await fs3.stat(parentDir);
|
|
7816
7943
|
if (!parentStats.isDirectory()) {
|
|
7817
7944
|
throw new Error(
|
|
7818
|
-
`Parent path '${parentDir}' exists but is not a directory. Cannot create
|
|
7945
|
+
`Parent path '${parentDir}' exists but is not a directory. Cannot create destination ${options.recursive ? "directory" : "file"} '${options.destination}'.`
|
|
7819
7946
|
);
|
|
7820
7947
|
}
|
|
7821
7948
|
} catch (error) {
|
|
7822
7949
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7823
7950
|
throw new Error(
|
|
7824
|
-
`Parent directory '${parentDir}' of
|
|
7951
|
+
`Parent directory '${parentDir}' of destination '${options.destination}' does not exist. Create it first or use a different destination path.`
|
|
7825
7952
|
);
|
|
7826
7953
|
}
|
|
7827
7954
|
throw error;
|
|
@@ -7829,7 +7956,7 @@ async function validateOutput(options) {
|
|
|
7829
7956
|
if (options.force) return;
|
|
7830
7957
|
let outputStats;
|
|
7831
7958
|
try {
|
|
7832
|
-
outputStats = await fs3.stat(options.
|
|
7959
|
+
outputStats = await fs3.stat(options.destination);
|
|
7833
7960
|
} catch (error) {
|
|
7834
7961
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
7835
7962
|
return;
|
|
@@ -7839,64 +7966,73 @@ async function validateOutput(options) {
|
|
|
7839
7966
|
const isDirectory = outputStats.isDirectory();
|
|
7840
7967
|
if (!options.recursive && isDirectory) {
|
|
7841
7968
|
throw new Error(
|
|
7842
|
-
`
|
|
7969
|
+
`destination path '${options.destination}' is a directory but expected a file path to download a single blob (--recursive not provided).`
|
|
7843
7970
|
);
|
|
7844
7971
|
}
|
|
7845
7972
|
if (options.recursive && !isDirectory) {
|
|
7846
7973
|
throw new Error(
|
|
7847
|
-
`
|
|
7974
|
+
`destination path '${options.destination}' exists but is not a directory. Directory downloads require a directory path.`
|
|
7848
7975
|
);
|
|
7849
7976
|
}
|
|
7850
7977
|
if (options.recursive) {
|
|
7851
|
-
const entries = await fs3.readdir(options.
|
|
7978
|
+
const entries = await fs3.readdir(options.destination);
|
|
7852
7979
|
if (entries.length > 0) {
|
|
7853
7980
|
throw new Error(
|
|
7854
|
-
`Directory '${options.
|
|
7981
|
+
`Directory '${options.destination}' exists and is not empty. Use --force to overwrite or choose an empty directory.`
|
|
7855
7982
|
);
|
|
7856
7983
|
}
|
|
7857
7984
|
} else {
|
|
7858
7985
|
throw new Error(
|
|
7859
|
-
`File '${options.
|
|
7986
|
+
`File '${options.destination}' already exists. Use --force to overwrite.`
|
|
7860
7987
|
);
|
|
7861
7988
|
}
|
|
7862
7989
|
}
|
|
7863
7990
|
async function createFileList(options, nodeClient, account) {
|
|
7864
7991
|
const matchingBlobList = [];
|
|
7992
|
+
const stripAccountPrefix = (fullName) => {
|
|
7993
|
+
const blobNameMatch = fullName.match(/^@[^/]+\/(.+)$/);
|
|
7994
|
+
return blobNameMatch ? blobNameMatch[1] : fullName;
|
|
7995
|
+
};
|
|
7865
7996
|
if (options.recursive) {
|
|
7866
|
-
const
|
|
7997
|
+
const activeBlobs = await nodeClient.coordination.getAccountBlobs({
|
|
7867
7998
|
account
|
|
7868
7999
|
});
|
|
7869
|
-
for (const blobMd of
|
|
7870
|
-
const blobNameWithoutAccount = blobMd.name
|
|
7871
|
-
if (!blobNameWithoutAccount.startsWith(options.
|
|
8000
|
+
for (const blobMd of activeBlobs) {
|
|
8001
|
+
const blobNameWithoutAccount = stripAccountPrefix(blobMd.name);
|
|
8002
|
+
if (!blobNameWithoutAccount.startsWith(options.source)) continue;
|
|
7872
8003
|
matchingBlobList.push(blobMd);
|
|
7873
8004
|
}
|
|
7874
8005
|
if (matchingBlobList.length === 0) {
|
|
7875
8006
|
throw new Error(
|
|
7876
|
-
`No blobs matching prefix ${options.
|
|
8007
|
+
`No active blobs matching prefix '${options.source}' were found for account ${account.toString()}. They may have expired or been deleted.`
|
|
7877
8008
|
);
|
|
7878
8009
|
}
|
|
7879
8010
|
} else {
|
|
7880
|
-
const
|
|
7881
|
-
account
|
|
7882
|
-
name: options.src
|
|
8011
|
+
const activeBlobs = await nodeClient.coordination.getAccountBlobs({
|
|
8012
|
+
account
|
|
7883
8013
|
});
|
|
7884
|
-
|
|
7885
|
-
|
|
8014
|
+
const md = activeBlobs.find(
|
|
8015
|
+
(blob) => stripAccountPrefix(blob.name) === options.source
|
|
8016
|
+
);
|
|
8017
|
+
if (!md) {
|
|
8018
|
+
throw new Error(
|
|
8019
|
+
`No active blob named '${options.source}' was found for account ${account.toString()}. It may have expired or been deleted.
|
|
8020
|
+
`
|
|
8021
|
+
);
|
|
7886
8022
|
}
|
|
7887
8023
|
matchingBlobList.push(md);
|
|
7888
8024
|
}
|
|
7889
8025
|
const ret = [];
|
|
7890
8026
|
for (const blobMd of matchingBlobList) {
|
|
7891
|
-
|
|
7892
|
-
if (options.recursive) {
|
|
7893
|
-
blobname = blobMd.name.slice(1 + 64 + 1);
|
|
7894
|
-
} else {
|
|
7895
|
-
blobname = blobMd.name;
|
|
7896
|
-
}
|
|
8027
|
+
const blobNameWithoutAccount = stripAccountPrefix(blobMd.name);
|
|
7897
8028
|
const entry = {
|
|
7898
|
-
filename: denormBlobName2(
|
|
7899
|
-
|
|
8029
|
+
filename: denormBlobName2(
|
|
8030
|
+
options.source,
|
|
8031
|
+
blobNameWithoutAccount,
|
|
8032
|
+
options.destination
|
|
8033
|
+
),
|
|
8034
|
+
blobname: blobNameWithoutAccount,
|
|
8035
|
+
// FIXME this is nasty
|
|
7900
8036
|
sizeBytes: blobMd.size
|
|
7901
8037
|
};
|
|
7902
8038
|
ret.push(entry);
|
|
@@ -7935,96 +8071,170 @@ function createProgressTransform(totalBytes, reporter) {
|
|
|
7935
8071
|
});
|
|
7936
8072
|
}
|
|
7937
8073
|
function downloadCommand(program) {
|
|
7938
|
-
program.command("download
|
|
8074
|
+
program.command("download").description("Download a file or directory from Shelby RPC").argument("[source]", "Source blob name or prefix").argument("[destination]", "Destination path").option(
|
|
7939
8075
|
"-r, --recursive",
|
|
7940
8076
|
"Download assuming canonical directory layout using '/' as separators. Produces a directory."
|
|
7941
|
-
).option("-f, --force", "Overwrite the
|
|
7942
|
-
async (
|
|
7943
|
-
|
|
7944
|
-
...rawOptions,
|
|
7945
|
-
src,
|
|
7946
|
-
dst
|
|
7947
|
-
});
|
|
7948
|
-
options.dst = path3.resolve(options.dst);
|
|
7949
|
-
await validateOutput(options);
|
|
7950
|
-
if (options.recursive) {
|
|
7951
|
-
console.log(
|
|
7952
|
-
`Downloading directory: ${options.src} -> ${options.dst}`
|
|
7953
|
-
);
|
|
7954
|
-
} else {
|
|
7955
|
-
console.log(`Downloading blob: ${options.src} -> ${options.dst}`);
|
|
7956
|
-
}
|
|
7957
|
-
const configPath = program.opts().configFile;
|
|
7958
|
-
let config;
|
|
8077
|
+
).option("-f, --force", "Overwrite the destination").action(
|
|
8078
|
+
async (source, destination, rawOptions) => {
|
|
8079
|
+
let options;
|
|
7959
8080
|
try {
|
|
7960
|
-
|
|
8081
|
+
options = DownloadOptionsSchema.parse({
|
|
8082
|
+
...rawOptions,
|
|
8083
|
+
source,
|
|
8084
|
+
destination
|
|
8085
|
+
});
|
|
7961
8086
|
} catch (error) {
|
|
7962
|
-
|
|
8087
|
+
if (error instanceof z13.ZodError) {
|
|
8088
|
+
const firstIssue = error.issues[0];
|
|
8089
|
+
if (firstIssue) {
|
|
8090
|
+
console.log(firstIssue.message);
|
|
8091
|
+
} else {
|
|
8092
|
+
console.error("\u26A0\uFE0F Invalid download options provided");
|
|
8093
|
+
}
|
|
8094
|
+
} else {
|
|
8095
|
+
console.error(
|
|
8096
|
+
`\u26A0\uFE0F ${error instanceof Error ? error.message : String(error)}`
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
7963
8099
|
process.exit(1);
|
|
7964
8100
|
}
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
const activeAccount = getCurrentAccount(
|
|
7969
|
-
config,
|
|
7970
|
-
program.opts().account
|
|
7971
|
-
).account;
|
|
7972
|
-
const nodeClient = new ShelbyNodeClient(shelbyConfig);
|
|
7973
|
-
const fileList = await createFileList(
|
|
7974
|
-
options,
|
|
7975
|
-
nodeClient,
|
|
7976
|
-
activeAccount.accountAddress
|
|
7977
|
-
);
|
|
7978
|
-
for (const fileEntry of fileList) {
|
|
7979
|
-
console.log(
|
|
7980
|
-
`Downloading ${fileEntry.blobname} -> ${fileEntry.filename}`
|
|
7981
|
-
);
|
|
7982
|
-
}
|
|
7983
|
-
if (options.force) {
|
|
7984
|
-
console.log(`--force was set, so deleting ${options.dst}`);
|
|
7985
|
-
await fs3.rm(options.dst, { recursive: true, force: true });
|
|
7986
|
-
}
|
|
8101
|
+
options.destination = path3.resolve(options.destination);
|
|
8102
|
+
let config;
|
|
8103
|
+
let spinner;
|
|
7987
8104
|
const handleSigint = () => {
|
|
7988
|
-
spinner
|
|
8105
|
+
spinner?.fail("Quitting due to ctrl-C / SIGINT...");
|
|
7989
8106
|
process.exit(1);
|
|
7990
8107
|
};
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
}).start();
|
|
7997
|
-
await createOutputDirectories(fileList);
|
|
7998
|
-
let totalSize = 0;
|
|
7999
|
-
for (const fileEntry of fileList) totalSize += fileEntry.sizeBytes;
|
|
8000
|
-
const startTime = performance.now();
|
|
8001
|
-
let amountDownloaded = 0;
|
|
8002
|
-
const formatProgressPercent = () => (100 * (amountDownloaded / totalSize)).toFixed(2);
|
|
8003
|
-
const formatProgressRate = () => {
|
|
8004
|
-
const mib = amountDownloaded / 1024 / 1024;
|
|
8005
|
-
const sec = (performance.now() - startTime) / 1e3;
|
|
8006
|
-
return (mib / sec).toFixed(2);
|
|
8007
|
-
};
|
|
8008
|
-
for (const fileEntry of fileList) {
|
|
8009
|
-
const out = fsS.createWriteStream(fileEntry.filename);
|
|
8010
|
-
const { readable } = await nodeClient.rpc.getBlob({
|
|
8011
|
-
account: activeAccount.accountAddress,
|
|
8012
|
-
blobName: fileEntry.blobname
|
|
8108
|
+
try {
|
|
8109
|
+
const configPath = program.opts().configFile;
|
|
8110
|
+
config = loadConfig(configPath);
|
|
8111
|
+
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8112
|
+
context: program.opts().context
|
|
8013
8113
|
});
|
|
8014
|
-
const
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8114
|
+
const activeAccount = getCurrentAccount(
|
|
8115
|
+
config,
|
|
8116
|
+
program.opts().account
|
|
8117
|
+
).account;
|
|
8118
|
+
const nodeClient = new ShelbyNodeClient(shelbyConfig);
|
|
8119
|
+
const fileList = await createFileList(
|
|
8120
|
+
options,
|
|
8121
|
+
nodeClient,
|
|
8122
|
+
activeAccount.accountAddress
|
|
8123
|
+
);
|
|
8124
|
+
await validateOutput(options);
|
|
8125
|
+
console.log(`
|
|
8126
|
+
\u{1F4E5} Downloading Files (${fileList.length} total)`);
|
|
8127
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8128
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
8129
|
+
const fileEntry = fileList[i];
|
|
8130
|
+
const blobBaseName = path3.basename(fileEntry.blobname);
|
|
8131
|
+
console.log(`${i + 1}. ${blobBaseName} \u2192 ${fileEntry.filename}`);
|
|
8132
|
+
}
|
|
8133
|
+
console.log();
|
|
8134
|
+
if (options.force) {
|
|
8135
|
+
console.log(`--force was set, so deleting ${options.destination}`);
|
|
8136
|
+
await fs3.rm(options.destination, { recursive: true, force: true });
|
|
8137
|
+
}
|
|
8138
|
+
process.removeAllListeners("SIGINT");
|
|
8139
|
+
process.on("SIGINT", handleSigint);
|
|
8140
|
+
spinner = ora({
|
|
8141
|
+
text: "Creating destination directory tree...",
|
|
8142
|
+
discardStdin: false
|
|
8143
|
+
}).start();
|
|
8144
|
+
await createOutputDirectories(fileList);
|
|
8145
|
+
let totalSize = 0;
|
|
8146
|
+
for (const fileEntry of fileList) totalSize += fileEntry.sizeBytes;
|
|
8147
|
+
const startTime = performance.now();
|
|
8148
|
+
let amountDownloaded = 0;
|
|
8149
|
+
let filesDownloaded = 0;
|
|
8150
|
+
const isSingleFile = fileList.length === 1;
|
|
8151
|
+
const formatProgressPercent = () => totalSize > 0 ? (100 * (amountDownloaded / totalSize)).toFixed(2) : "0.00";
|
|
8152
|
+
for (const fileEntry of fileList) {
|
|
8153
|
+
const out = fsS.createWriteStream(fileEntry.filename);
|
|
8154
|
+
const fileName = path3.basename(fileEntry.blobname);
|
|
8155
|
+
const { readable } = await nodeClient.rpc.getBlob({
|
|
8156
|
+
account: activeAccount.accountAddress,
|
|
8157
|
+
blobName: fileEntry.blobname
|
|
8158
|
+
});
|
|
8159
|
+
const reporter = (filePercent, _rateMiB) => {
|
|
8160
|
+
if (spinner) {
|
|
8161
|
+
if (isSingleFile) {
|
|
8162
|
+
spinner.text = `Downloading ${fileName}... (${filePercent}%)`;
|
|
8163
|
+
} else {
|
|
8164
|
+
spinner.text = `Downloading ${fileName}... (${filesDownloaded}/${fileList.length} files, ${formatProgressPercent()}%)`;
|
|
8165
|
+
}
|
|
8166
|
+
}
|
|
8167
|
+
};
|
|
8168
|
+
const nodeStream = Readable2.fromWeb(readable);
|
|
8169
|
+
await pipeline(
|
|
8170
|
+
nodeStream,
|
|
8171
|
+
createProgressTransform(fileEntry.sizeBytes, reporter),
|
|
8172
|
+
out
|
|
8173
|
+
);
|
|
8174
|
+
amountDownloaded += fileEntry.sizeBytes;
|
|
8175
|
+
filesDownloaded++;
|
|
8176
|
+
}
|
|
8177
|
+
const totalTime = (performance.now() - startTime) / 1e3;
|
|
8178
|
+
spinner?.stop();
|
|
8179
|
+
console.log(
|
|
8180
|
+
`
|
|
8181
|
+
\u2705 All downloads complete \u2014 took ${totalTime.toFixed(1)} seconds`
|
|
8022
8182
|
);
|
|
8023
|
-
|
|
8183
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8184
|
+
console.log("\u2728 Done!");
|
|
8185
|
+
} catch (error) {
|
|
8186
|
+
spinner?.stop();
|
|
8187
|
+
console.log("\n\u274C Download Failed");
|
|
8188
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8189
|
+
if (options.recursive) {
|
|
8190
|
+
console.log(`\u{1F9FE} Directory: ${options.source}`);
|
|
8191
|
+
} else {
|
|
8192
|
+
console.log(`\u{1F9FE} File: ${options.source}`);
|
|
8193
|
+
}
|
|
8194
|
+
if (config) {
|
|
8195
|
+
const activeAccount = getCurrentAccount(
|
|
8196
|
+
config,
|
|
8197
|
+
program.opts().account
|
|
8198
|
+
).account;
|
|
8199
|
+
console.log(
|
|
8200
|
+
`\u{1F464} Account: ${activeAccount.accountAddress.toString()}`
|
|
8201
|
+
);
|
|
8202
|
+
}
|
|
8203
|
+
console.log();
|
|
8204
|
+
if (error instanceof Error) {
|
|
8205
|
+
if (error.message.includes("GraphQL Error (Code: 401)")) {
|
|
8206
|
+
const contextName = program.opts().context || config?.default_context;
|
|
8207
|
+
console.error("\u26A0\uFE0F Authentication required.");
|
|
8208
|
+
console.error(
|
|
8209
|
+
" Please add an indexer API key to your context configuration."
|
|
8210
|
+
);
|
|
8211
|
+
console.error();
|
|
8212
|
+
console.error("\u{1F4A1} Tip: Update your context with an API key:");
|
|
8213
|
+
console.error(
|
|
8214
|
+
` \u{1F449} shelby context update ${contextName ?? "default"} --indexer-api-key YOUR_KEY`
|
|
8215
|
+
);
|
|
8216
|
+
} else if (error.message.includes("No active blob") || error.message.includes("may have expired or been deleted")) {
|
|
8217
|
+
const blobName = options.source;
|
|
8218
|
+
console.error(
|
|
8219
|
+
`\u26A0\uFE0F No active blob${options.recursive ? "s" : ""} named '${blobName}' ${options.recursive ? "were" : "was"} found.`
|
|
8220
|
+
);
|
|
8221
|
+
console.error(" It may have expired or been deleted.");
|
|
8222
|
+
console.error();
|
|
8223
|
+
console.error(
|
|
8224
|
+
"\u{1F4A1} Tip: Try listing available blobs before downloading:"
|
|
8225
|
+
);
|
|
8226
|
+
console.error(" \u{1F449} shelby account blobs");
|
|
8227
|
+
} else {
|
|
8228
|
+
console.error(`\u26A0\uFE0F ${error.message}`);
|
|
8229
|
+
}
|
|
8230
|
+
} else {
|
|
8231
|
+
console.error(`\u26A0\uFE0F ${String(error)}`);
|
|
8232
|
+
}
|
|
8233
|
+
console.log("\n");
|
|
8234
|
+
process.exit(1);
|
|
8235
|
+
} finally {
|
|
8236
|
+
process.removeListener("SIGINT", handleSigint);
|
|
8024
8237
|
}
|
|
8025
|
-
const totalTime = (performance.now() - startTime) / 1e3;
|
|
8026
|
-
const totalRate = amountDownloaded / 1024 / 1024 / totalTime;
|
|
8027
|
-
spinner.succeed(`Final rate ${totalRate.toFixed(2)} MiB/s`);
|
|
8028
8238
|
}
|
|
8029
8239
|
);
|
|
8030
8240
|
}
|
|
@@ -8246,13 +8456,63 @@ import * as path5 from "path";
|
|
|
8246
8456
|
import { Aptos as Aptos5, AptosConfig as AptosConfig4 } from "@aptos-labs/ts-sdk";
|
|
8247
8457
|
import * as chrono from "chrono-node";
|
|
8248
8458
|
import { glob } from "glob";
|
|
8459
|
+
import ignore from "ignore";
|
|
8249
8460
|
import { Box as Box7, render as render4, Text as Text7 } from "ink";
|
|
8250
8461
|
import SelectInput4 from "ink-select-input";
|
|
8251
8462
|
import ora2 from "ora";
|
|
8252
8463
|
import { z as z15 } from "zod";
|
|
8464
|
+
|
|
8465
|
+
// src/utils/commander-helpers.ts
|
|
8466
|
+
function createExitOverrideHandler(commandName, requiredArgs, requiredOptions, exampleUsage, warningMessage) {
|
|
8467
|
+
return (err) => {
|
|
8468
|
+
if (err.code === "commander.helpDisplayed") {
|
|
8469
|
+
process.exit(0);
|
|
8470
|
+
}
|
|
8471
|
+
const args = process.argv.slice(process.argv.indexOf(commandName) + 1);
|
|
8472
|
+
const hasArguments = args.some((arg) => !arg.startsWith("-"));
|
|
8473
|
+
if (err.code === "commander.missingMandatoryOptionValue") {
|
|
8474
|
+
if (!hasArguments && requiredOptions) {
|
|
8475
|
+
console.error("\n\u274C Missing Required Arguments");
|
|
8476
|
+
console.error("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8477
|
+
const warning = warningMessage || "Missing required arguments or options";
|
|
8478
|
+
console.error(`\u26A0\uFE0F ${warning}
|
|
8479
|
+
`);
|
|
8480
|
+
console.error("\u{1F4A1} Usage:");
|
|
8481
|
+
console.error(
|
|
8482
|
+
` shelby ${commandName} ${requiredArgs} ${requiredOptions}
|
|
8483
|
+
`
|
|
8484
|
+
);
|
|
8485
|
+
if (exampleUsage) {
|
|
8486
|
+
console.error("\u{1F4DD} Examples:");
|
|
8487
|
+
console.error(` ${exampleUsage}`);
|
|
8488
|
+
}
|
|
8489
|
+
console.error("\n");
|
|
8490
|
+
} else {
|
|
8491
|
+
console.error(err.message);
|
|
8492
|
+
}
|
|
8493
|
+
process.exit(1);
|
|
8494
|
+
}
|
|
8495
|
+
if (err.code === "commander.optionMissingArgument") {
|
|
8496
|
+
console.error("\n\u274C Missing Option Value");
|
|
8497
|
+
console.error("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
8498
|
+
const cleanMessage = err.message.replace(/^error:\s*/i, "");
|
|
8499
|
+
console.error(`\u26A0\uFE0F ${cleanMessage}
|
|
8500
|
+
`);
|
|
8501
|
+
if (exampleUsage) {
|
|
8502
|
+
console.error("\u{1F4A1} Example:");
|
|
8503
|
+
console.error(` ${exampleUsage}`);
|
|
8504
|
+
}
|
|
8505
|
+
console.error("\n");
|
|
8506
|
+
process.exit(1);
|
|
8507
|
+
}
|
|
8508
|
+
throw err;
|
|
8509
|
+
};
|
|
8510
|
+
}
|
|
8511
|
+
|
|
8512
|
+
// src/commands/upload.tsx
|
|
8253
8513
|
import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8254
8514
|
var normBlobName2 = (i, f, b) => normBlobName(path5, i, f, b);
|
|
8255
|
-
var flexibleDateSchema = z15.string().transform((val) => {
|
|
8515
|
+
var flexibleDateSchema = z15.string().transform((val, ctx) => {
|
|
8256
8516
|
const now = /* @__PURE__ */ new Date();
|
|
8257
8517
|
let parsedDate = null;
|
|
8258
8518
|
if (/^\d+$/.test(val)) {
|
|
@@ -8274,77 +8534,207 @@ var flexibleDateSchema = z15.string().transform((val) => {
|
|
|
8274
8534
|
}
|
|
8275
8535
|
}
|
|
8276
8536
|
if (!parsedDate || Number.isNaN(parsedDate.getTime())) {
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8537
|
+
ctx.addIssue({
|
|
8538
|
+
code: z15.ZodIssueCode.custom,
|
|
8539
|
+
message: `
|
|
8540
|
+
\u274C Upload Failed
|
|
8541
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8542
|
+
|
|
8543
|
+
\u26A0\uFE0F Invalid date format: "${val}"
|
|
8544
|
+
|
|
8545
|
+
\u{1F4A1} Try formats like:
|
|
8546
|
+
\u2022 "tomorrow", "in 2 days", "next Friday"
|
|
8547
|
+
\u2022 "2025-12-31"
|
|
8548
|
+
\u2022 UNIX timestamp
|
|
8549
|
+
|
|
8550
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
8551
|
+
});
|
|
8552
|
+
return z15.NEVER;
|
|
8280
8553
|
}
|
|
8281
8554
|
if (parsedDate.getTime() <= now.getTime()) {
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8555
|
+
ctx.addIssue({
|
|
8556
|
+
code: z15.ZodIssueCode.custom,
|
|
8557
|
+
message: `
|
|
8558
|
+
\u274C Upload Failed
|
|
8559
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8560
|
+
|
|
8561
|
+
\u26A0\uFE0F Expiration date must be in the future
|
|
8562
|
+
|
|
8563
|
+
\u{1F4C5} "${val}" resolves to ${parsedDate.toLocaleString()}
|
|
8564
|
+
which is in the past.
|
|
8565
|
+
|
|
8566
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
8567
|
+
});
|
|
8568
|
+
return z15.NEVER;
|
|
8285
8569
|
}
|
|
8286
8570
|
return parsedDate;
|
|
8287
8571
|
});
|
|
8288
8572
|
var UploadOptionsSchema = z15.object({
|
|
8289
|
-
|
|
8290
|
-
|
|
8573
|
+
source: z15.string({
|
|
8574
|
+
required_error: '\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing source file or directory path (first argument)\n\n\u{1F4A1} Usage:\n shelby upload <source-file-or-directory> <destination-blob-name> [options]\n\n\u{1F4DD} Examples:\n shelby upload ./myfile.txt my-blob.txt -e tomorrow\n shelby upload ./my-folder/ my-folder/ -r -e "next week"\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'
|
|
8575
|
+
}).min(1, "Source file or directory path is required"),
|
|
8576
|
+
destination: z15.string({
|
|
8577
|
+
required_error: "\n\u274C Missing Required Argument\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Missing destination blob name (second argument)\n\n\u{1F4A1} Usage:\n shelby upload <source-file-or-directory> <destination-blob-name> [options]\n\n\u{1F4DD} Example:\n shelby upload ./myfile.txt files/my-blob.txt -e tomorrow\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
8578
|
+
}).min(1, "Destination blob name is required"),
|
|
8291
8579
|
expiration: flexibleDateSchema,
|
|
8292
8580
|
recursive: z15.boolean().optional().default(false),
|
|
8293
8581
|
assumeYes: z15.boolean().optional().default(false),
|
|
8294
8582
|
outputCommitments: z15.string().optional()
|
|
8295
8583
|
}).superRefine(async (data, ctx) => {
|
|
8296
|
-
const stats = await fs5.stat(data.
|
|
8584
|
+
const stats = await fs5.stat(data.source);
|
|
8297
8585
|
if (!stats.isFile() && !stats.isDirectory()) {
|
|
8298
8586
|
ctx.addIssue({
|
|
8299
8587
|
code: z15.ZodIssueCode.custom,
|
|
8300
|
-
message: "
|
|
8301
|
-
path: ["
|
|
8588
|
+
message: "\n\u274C Upload Failed\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n\u26A0\uFE0F Source path must be a file or directory\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
8589
|
+
path: ["source"]
|
|
8302
8590
|
});
|
|
8303
8591
|
return;
|
|
8304
8592
|
}
|
|
8305
8593
|
if (stats.isDirectory()) {
|
|
8306
|
-
if (!data.
|
|
8594
|
+
if (!data.destination.endsWith("/")) {
|
|
8307
8595
|
ctx.addIssue({
|
|
8308
8596
|
code: z15.ZodIssueCode.custom,
|
|
8309
|
-
message:
|
|
8310
|
-
|
|
8597
|
+
message: `
|
|
8598
|
+
\u274C Upload Failed
|
|
8599
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8600
|
+
|
|
8601
|
+
\u26A0\uFE0F When source is a directory, destination must end with '/'
|
|
8602
|
+
|
|
8603
|
+
\u{1F4A1} Tip: When uploading a directory, add a trailing slash:
|
|
8604
|
+
\u{1F449} shelby upload -r ${data.source} ${data.destination}/
|
|
8605
|
+
|
|
8606
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
8607
|
+
path: ["destination"]
|
|
8311
8608
|
});
|
|
8312
8609
|
}
|
|
8313
8610
|
} else if (stats.isFile()) {
|
|
8314
|
-
const blobNameResult = BlobNameSchema.safeParse(data.
|
|
8611
|
+
const blobNameResult = BlobNameSchema.safeParse(data.destination);
|
|
8315
8612
|
if (!blobNameResult.success) {
|
|
8316
8613
|
ctx.addIssue({
|
|
8317
8614
|
code: z15.ZodIssueCode.custom,
|
|
8318
|
-
message:
|
|
8319
|
-
|
|
8615
|
+
message: `
|
|
8616
|
+
\u274C Upload Failed
|
|
8617
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
8618
|
+
|
|
8619
|
+
\u26A0\uFE0F When source is a file, destination must be a valid blob name (cannot end with '/')
|
|
8620
|
+
|
|
8621
|
+
\u{1F4A1} Tip: When uploading a file, remove the trailing slash:
|
|
8622
|
+
\u{1F449} shelby upload ${data.source} ${data.destination.replace(/\/$/, "")}
|
|
8623
|
+
|
|
8624
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`,
|
|
8625
|
+
path: ["destination"]
|
|
8320
8626
|
});
|
|
8321
8627
|
}
|
|
8322
8628
|
}
|
|
8323
8629
|
});
|
|
8630
|
+
async function buildGitignoreMap(root) {
|
|
8631
|
+
const resolvedRoot = path5.resolve(root);
|
|
8632
|
+
const gitignoreMap = /* @__PURE__ */ new Map();
|
|
8633
|
+
const ensureMatcher = (dir) => {
|
|
8634
|
+
const existing = gitignoreMap.get(dir);
|
|
8635
|
+
if (existing) {
|
|
8636
|
+
return existing;
|
|
8637
|
+
}
|
|
8638
|
+
const instance = ignore();
|
|
8639
|
+
gitignoreMap.set(dir, instance);
|
|
8640
|
+
return instance;
|
|
8641
|
+
};
|
|
8642
|
+
ensureMatcher(resolvedRoot).add(".git/");
|
|
8643
|
+
const gitignoreFiles = await glob("**/.gitignore", {
|
|
8644
|
+
cwd: resolvedRoot,
|
|
8645
|
+
dot: true,
|
|
8646
|
+
absolute: true,
|
|
8647
|
+
nodir: true
|
|
8648
|
+
});
|
|
8649
|
+
for (const gitignorePath of gitignoreFiles) {
|
|
8650
|
+
try {
|
|
8651
|
+
const contents = await fs5.readFile(gitignorePath, "utf8");
|
|
8652
|
+
const directory = path5.dirname(gitignorePath);
|
|
8653
|
+
ensureMatcher(directory).add(contents);
|
|
8654
|
+
} catch (error) {
|
|
8655
|
+
if (error instanceof Error && Object.hasOwn(error, "code") && // biome-ignore lint/suspicious/noExplicitAny: node fs error type
|
|
8656
|
+
error.code === "ENOENT") {
|
|
8657
|
+
continue;
|
|
8658
|
+
}
|
|
8659
|
+
throw error;
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
return gitignoreMap;
|
|
8663
|
+
}
|
|
8664
|
+
function collectDirectoriesFromRoot(root, filePath) {
|
|
8665
|
+
const directories = [];
|
|
8666
|
+
let current = path5.dirname(filePath);
|
|
8667
|
+
while (true) {
|
|
8668
|
+
directories.push(current);
|
|
8669
|
+
if (current === root) {
|
|
8670
|
+
break;
|
|
8671
|
+
}
|
|
8672
|
+
const parent = path5.dirname(current);
|
|
8673
|
+
if (parent === current) {
|
|
8674
|
+
break;
|
|
8675
|
+
}
|
|
8676
|
+
current = parent;
|
|
8677
|
+
}
|
|
8678
|
+
return directories.reverse();
|
|
8679
|
+
}
|
|
8680
|
+
function shouldIgnorePath(gitignoreMap, root, filePath) {
|
|
8681
|
+
const directories = collectDirectoriesFromRoot(root, filePath);
|
|
8682
|
+
let ignored = false;
|
|
8683
|
+
for (const directory of directories) {
|
|
8684
|
+
const matcher = gitignoreMap.get(directory);
|
|
8685
|
+
if (!matcher) {
|
|
8686
|
+
continue;
|
|
8687
|
+
}
|
|
8688
|
+
const relative2 = path5.relative(directory, filePath).split(path5.sep).join("/");
|
|
8689
|
+
const result = matcher.test(relative2);
|
|
8690
|
+
if (result.ignored) {
|
|
8691
|
+
ignored = true;
|
|
8692
|
+
}
|
|
8693
|
+
if (result.unignored) {
|
|
8694
|
+
ignored = false;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
return ignored;
|
|
8698
|
+
}
|
|
8324
8699
|
async function createFilelist(options) {
|
|
8325
|
-
const stats = await fs5.stat(options.
|
|
8700
|
+
const stats = await fs5.stat(options.source);
|
|
8326
8701
|
if (stats.isFile()) {
|
|
8327
|
-
const blobname = normBlobName2(
|
|
8702
|
+
const blobname = normBlobName2(
|
|
8703
|
+
options.source,
|
|
8704
|
+
options.source,
|
|
8705
|
+
options.destination
|
|
8706
|
+
);
|
|
8328
8707
|
const blobNameValidation = BlobNameSchema.safeParse(blobname);
|
|
8329
8708
|
if (!blobNameValidation.success) {
|
|
8330
8709
|
throw new Error(
|
|
8331
|
-
`File ${options.
|
|
8710
|
+
`File ${options.source} as blobname ${blobname} would form an invalid blob name: ${blobNameValidation.error.message}`
|
|
8332
8711
|
);
|
|
8333
8712
|
}
|
|
8334
|
-
return [{ filename: options.
|
|
8713
|
+
return [{ filename: options.source, blobname, sizeBytes: stats.size }];
|
|
8335
8714
|
}
|
|
8336
8715
|
if (!options.recursive) {
|
|
8337
8716
|
throw new Error(
|
|
8338
|
-
`${options.
|
|
8717
|
+
`${options.source} is a directory. Use --recursive to upload directories.`
|
|
8339
8718
|
);
|
|
8340
8719
|
}
|
|
8720
|
+
const sourceRoot = path5.resolve(options.source);
|
|
8721
|
+
const gitignoreMap = await buildGitignoreMap(sourceRoot);
|
|
8341
8722
|
const fileList = [];
|
|
8342
|
-
const result = await glob(
|
|
8343
|
-
|
|
8723
|
+
const result = await glob("**/*", {
|
|
8724
|
+
cwd: sourceRoot,
|
|
8725
|
+
absolute: true,
|
|
8726
|
+
nodir: true,
|
|
8727
|
+
dot: true
|
|
8344
8728
|
});
|
|
8345
8729
|
for await (const file of result) {
|
|
8730
|
+
if (shouldIgnorePath(gitignoreMap, sourceRoot, file)) {
|
|
8731
|
+
continue;
|
|
8732
|
+
}
|
|
8346
8733
|
const stats2 = await fs5.stat(file);
|
|
8347
|
-
|
|
8734
|
+
if (!stats2.isFile()) {
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
const blobname = normBlobName2(sourceRoot, file, options.destination);
|
|
8348
8738
|
const blobNameValidation = BlobNameSchema.safeParse(blobname);
|
|
8349
8739
|
if (!blobNameValidation.success) {
|
|
8350
8740
|
throw new Error(
|
|
@@ -8360,7 +8750,7 @@ async function createFilelist(options) {
|
|
|
8360
8750
|
return fileList;
|
|
8361
8751
|
}
|
|
8362
8752
|
function uploadCommand(program) {
|
|
8363
|
-
program.command("upload
|
|
8753
|
+
const uploadCmd = program.command("upload").description("Upload a file or directory to the shelby RPC in the config.").argument("[source]", "Source file or directory path").argument("[destination]", "Destination blob name").requiredOption(
|
|
8364
8754
|
"-e, --expiration <datetime>",
|
|
8365
8755
|
'Expiration date/time (required). Examples: "tomorrow", "in 2 days", "next Friday", "2025-12-31", UNIX timestamp'
|
|
8366
8756
|
).option("-r, --recursive", "If uploading a directory, recurse").option(
|
|
@@ -8369,205 +8759,227 @@ function uploadCommand(program) {
|
|
|
8369
8759
|
).option(
|
|
8370
8760
|
"--output-commitments <filename>",
|
|
8371
8761
|
"Location to store commitments computed as as part of the upload"
|
|
8372
|
-
).
|
|
8373
|
-
|
|
8374
|
-
try {
|
|
8375
|
-
validatedOptions = await UploadOptionsSchema.parseAsync({
|
|
8376
|
-
...options,
|
|
8377
|
-
src,
|
|
8378
|
-
dst
|
|
8379
|
-
});
|
|
8380
|
-
} catch (error) {
|
|
8381
|
-
const { displayMessage } = handleError(error);
|
|
8382
|
-
console.error(`\u274C ${displayMessage}`);
|
|
8383
|
-
process.exit(1);
|
|
8384
|
-
}
|
|
8385
|
-
const configPath = program.opts().configFile;
|
|
8386
|
-
let config;
|
|
8387
|
-
try {
|
|
8388
|
-
config = loadConfig(configPath);
|
|
8389
|
-
} catch (error) {
|
|
8390
|
-
console.error(`Error: ${error.message}`);
|
|
8391
|
-
process.exit(1);
|
|
8762
|
+
).configureOutput({
|
|
8763
|
+
writeErr: (_str) => {
|
|
8392
8764
|
}
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
console.log("");
|
|
8407
|
-
console.log(
|
|
8408
|
-
`\u{1F9EE} Filelist created (${filelist.length} ${filelist.length === 1 ? "entry" : "entries"})`
|
|
8409
|
-
);
|
|
8410
|
-
console.log(`\u23F1\uFE0F Took: ${timeToCreateFilelist}s`);
|
|
8411
|
-
if (validatedOptions.assumeYes) {
|
|
8412
|
-
console.log("\u2699\uFE0F Flag: --assume-yes (auto-confirmed)");
|
|
8413
|
-
}
|
|
8414
|
-
if (!validatedOptions.assumeYes) {
|
|
8415
|
-
const shouldContinue = await new Promise((resolve2) => {
|
|
8416
|
-
const { unmount } = render4(
|
|
8417
|
-
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
8418
|
-
/* @__PURE__ */ jsx13(Text7, { children: "Continue?" }),
|
|
8419
|
-
/* @__PURE__ */ jsx13(
|
|
8420
|
-
SelectInput4,
|
|
8421
|
-
{
|
|
8422
|
-
items: [
|
|
8423
|
-
{ label: "Yes", value: true },
|
|
8424
|
-
{ label: "No", value: false }
|
|
8425
|
-
],
|
|
8426
|
-
onSelect: (item) => {
|
|
8427
|
-
unmount();
|
|
8428
|
-
resolve2(item.value);
|
|
8429
|
-
}
|
|
8430
|
-
}
|
|
8431
|
-
)
|
|
8432
|
-
] })
|
|
8433
|
-
);
|
|
8434
|
-
});
|
|
8435
|
-
if (!shouldContinue) {
|
|
8436
|
-
console.log("Upload cancelled.");
|
|
8437
|
-
process.exit(0);
|
|
8438
|
-
}
|
|
8439
|
-
}
|
|
8440
|
-
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8441
|
-
context: program.opts().context
|
|
8442
|
-
});
|
|
8443
|
-
const activeAccount = getCurrentAccount(
|
|
8444
|
-
config,
|
|
8445
|
-
program.opts().account
|
|
8446
|
-
).account;
|
|
8447
|
-
const aptos = new Aptos5(new AptosConfig4(shelbyConfig.aptos));
|
|
8448
|
-
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
8449
|
-
const handleSigint = () => {
|
|
8450
|
-
spinner.fail("Quitting due to ctrl-C / SIGINT...");
|
|
8451
|
-
process.exit(1);
|
|
8452
|
-
};
|
|
8453
|
-
process.removeAllListeners("SIGINT");
|
|
8454
|
-
process.on("SIGINT", handleSigint);
|
|
8455
|
-
const expireUsec = validatedOptions.expiration.getTime() * 1e3;
|
|
8456
|
-
const expirationDate = new Date(expireUsec / 1e3);
|
|
8457
|
-
const formattedExpiration = expirationDate.toLocaleDateString("en-US", {
|
|
8458
|
-
year: "numeric",
|
|
8459
|
-
month: "short",
|
|
8460
|
-
day: "numeric",
|
|
8461
|
-
hour: "numeric",
|
|
8462
|
-
minute: "2-digit",
|
|
8463
|
-
second: "2-digit",
|
|
8464
|
-
hour12: true
|
|
8465
|
-
});
|
|
8466
|
-
console.log("");
|
|
8467
|
-
console.log(`\u{1F552} Expires: ${formattedExpiration}`);
|
|
8468
|
-
const spinner = ora2({
|
|
8469
|
-
text: "Uploading...",
|
|
8470
|
-
discardStdin: false
|
|
8471
|
-
}).start();
|
|
8472
|
-
const startTime = performance.now();
|
|
8473
|
-
let totalSize = 0;
|
|
8474
|
-
let amountUploaded = 0;
|
|
8475
|
-
const outputCommitments = {};
|
|
8476
|
-
let firstTransactionHash;
|
|
8477
|
-
const formatProgressPercent = () => (100 * (amountUploaded / totalSize)).toFixed(2);
|
|
8478
|
-
for (const entry of filelist) {
|
|
8479
|
-
totalSize += entry.sizeBytes;
|
|
8480
|
-
}
|
|
8481
|
-
let hasUploadedBlob = false;
|
|
8482
|
-
for (const entry of filelist) {
|
|
8483
|
-
const fileName = path5.basename(entry.filename);
|
|
8484
|
-
spinner.text = `\u{1F4D6} Reading ${fileName}... (${formatProgressPercent()}%)`;
|
|
8765
|
+
});
|
|
8766
|
+
uploadCmd.exitOverride(
|
|
8767
|
+
createExitOverrideHandler(
|
|
8768
|
+
"upload",
|
|
8769
|
+
"<source> <destination>",
|
|
8770
|
+
"-e, --expiration <datetime>",
|
|
8771
|
+
'shelby upload ./file.txt myblob -e "in 2 days"\n shelby upload ./my-folder/ my-folder/ -r -e tomorrow',
|
|
8772
|
+
"Missing source, destination, and expiration"
|
|
8773
|
+
)
|
|
8774
|
+
);
|
|
8775
|
+
uploadCmd.action(
|
|
8776
|
+
async (source, destination, options) => {
|
|
8777
|
+
let validatedOptions;
|
|
8485
8778
|
try {
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
);
|
|
8491
|
-
}
|
|
8492
|
-
spinner.text = `\u{1F517} Checking if blob exists... (${formatProgressPercent()}%)`;
|
|
8493
|
-
const existingBlobMetadata = await shelbyClient.coordination.getBlobMetadata({
|
|
8494
|
-
account: activeAccount.accountAddress,
|
|
8495
|
-
name: entry.blobname
|
|
8779
|
+
validatedOptions = await UploadOptionsSchema.parseAsync({
|
|
8780
|
+
...options,
|
|
8781
|
+
source,
|
|
8782
|
+
destination
|
|
8496
8783
|
});
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
)
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
}
|
|
8509
|
-
spinner.text = `\u{1F517} Registering ${fileName} on L1... (${formatProgressPercent()}%)`;
|
|
8510
|
-
const { transaction: pendingRegisterBlobTransaction } = await shelbyClient.coordination.registerBlob({
|
|
8511
|
-
account: activeAccount,
|
|
8512
|
-
blobName: entry.blobname,
|
|
8513
|
-
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
8514
|
-
size: blobData.length,
|
|
8515
|
-
expirationMicros: validatedOptions.expiration.getTime() * 1e3
|
|
8516
|
-
});
|
|
8517
|
-
const registerTransactionHash = pendingRegisterBlobTransaction.hash;
|
|
8518
|
-
if (!firstTransactionHash) {
|
|
8519
|
-
firstTransactionHash = registerTransactionHash;
|
|
8784
|
+
} catch (error) {
|
|
8785
|
+
if (error instanceof z15.ZodError) {
|
|
8786
|
+
const firstIssue = error.issues[0];
|
|
8787
|
+
if (firstIssue) {
|
|
8788
|
+
console.log(firstIssue.message);
|
|
8789
|
+
} else {
|
|
8790
|
+
console.error("\u26A0\uFE0F Invalid options provided");
|
|
8791
|
+
}
|
|
8792
|
+
} else {
|
|
8793
|
+
const { displayMessage } = handleError(error);
|
|
8794
|
+
console.error(`\u26A0\uFE0F ${displayMessage}`);
|
|
8520
8795
|
}
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
blobName: entry.blobname,
|
|
8528
|
-
blobData
|
|
8529
|
-
});
|
|
8530
|
-
hasUploadedBlob = true;
|
|
8531
|
-
amountUploaded += blobData.length;
|
|
8796
|
+
process.exit(1);
|
|
8797
|
+
}
|
|
8798
|
+
const configPath = program.opts().configFile;
|
|
8799
|
+
let config;
|
|
8800
|
+
try {
|
|
8801
|
+
config = loadConfig(configPath);
|
|
8532
8802
|
} catch (error) {
|
|
8533
|
-
|
|
8534
|
-
spinner.fail(displayMessage);
|
|
8803
|
+
console.error(`Error: ${error.message}`);
|
|
8535
8804
|
process.exit(1);
|
|
8536
8805
|
}
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
JSON.stringify(outputCommitments)
|
|
8806
|
+
const start = performance.now();
|
|
8807
|
+
const filelist = await createFilelist(validatedOptions);
|
|
8808
|
+
const timeToCreateFilelist = ((performance.now() - start) / 1e3).toFixed(
|
|
8809
|
+
5
|
|
8542
8810
|
);
|
|
8543
|
-
|
|
8544
|
-
|
|
8811
|
+
console.log("\n\u{1F680} Upload Summary");
|
|
8812
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8813
|
+
if (filelist.length === 1) {
|
|
8814
|
+
console.log(`\u{1F4E6} File: ${filelist[0].filename}`);
|
|
8815
|
+
console.log(`\u{1F4C1} Blob Name: ${filelist[0].blobname}`);
|
|
8816
|
+
} else {
|
|
8817
|
+
console.log(`\u{1F4E6} Files: ${filelist.length} files`);
|
|
8818
|
+
}
|
|
8819
|
+
console.log("");
|
|
8820
|
+
console.log(
|
|
8821
|
+
`\u{1F9EE} Filelist created (${filelist.length} ${filelist.length === 1 ? "entry" : "entries"})`
|
|
8822
|
+
);
|
|
8823
|
+
console.log(`\u23F1\uFE0F Took: ${timeToCreateFilelist}s`);
|
|
8824
|
+
if (validatedOptions.assumeYes) {
|
|
8825
|
+
console.log("\u2699\uFE0F Flag: --assume-yes (auto-confirmed)");
|
|
8826
|
+
}
|
|
8827
|
+
if (!validatedOptions.assumeYes) {
|
|
8828
|
+
const shouldContinue = await new Promise((resolve3) => {
|
|
8829
|
+
const { unmount } = render4(
|
|
8830
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
8831
|
+
/* @__PURE__ */ jsx13(Text7, { children: "Continue?" }),
|
|
8832
|
+
/* @__PURE__ */ jsx13(
|
|
8833
|
+
SelectInput4,
|
|
8834
|
+
{
|
|
8835
|
+
items: [
|
|
8836
|
+
{ label: "Yes", value: true },
|
|
8837
|
+
{ label: "No", value: false }
|
|
8838
|
+
],
|
|
8839
|
+
onSelect: (item) => {
|
|
8840
|
+
unmount();
|
|
8841
|
+
resolve3(item.value);
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
)
|
|
8845
|
+
] })
|
|
8846
|
+
);
|
|
8847
|
+
});
|
|
8848
|
+
if (!shouldContinue) {
|
|
8849
|
+
console.log("Upload cancelled.");
|
|
8850
|
+
process.exit(0);
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8853
|
+
const shelbyConfig = getCurrentShelbyConfig(config, {
|
|
8854
|
+
context: program.opts().context
|
|
8855
|
+
});
|
|
8856
|
+
const activeAccount = getCurrentAccount(
|
|
8857
|
+
config,
|
|
8858
|
+
program.opts().account
|
|
8859
|
+
).account;
|
|
8860
|
+
const aptos = new Aptos5(new AptosConfig4(shelbyConfig.aptos));
|
|
8861
|
+
const shelbyClient = new ShelbyNodeClient(shelbyConfig);
|
|
8862
|
+
const handleSigint = () => {
|
|
8863
|
+
spinner.fail("Quitting due to ctrl-C / SIGINT...");
|
|
8864
|
+
process.exit(1);
|
|
8865
|
+
};
|
|
8866
|
+
process.removeAllListeners("SIGINT");
|
|
8867
|
+
process.on("SIGINT", handleSigint);
|
|
8868
|
+
const expireUsec = validatedOptions.expiration.getTime() * 1e3;
|
|
8869
|
+
const expirationDate = new Date(expireUsec / 1e3);
|
|
8870
|
+
const formattedExpiration = expirationDate.toLocaleDateString("en-US", {
|
|
8871
|
+
year: "numeric",
|
|
8872
|
+
month: "short",
|
|
8873
|
+
day: "numeric",
|
|
8874
|
+
hour: "numeric",
|
|
8875
|
+
minute: "2-digit",
|
|
8876
|
+
second: "2-digit",
|
|
8877
|
+
hour12: true
|
|
8878
|
+
});
|
|
8879
|
+
console.log("");
|
|
8880
|
+
console.log(`\u{1F552} Expires: ${formattedExpiration}`);
|
|
8881
|
+
const spinner = ora2({
|
|
8882
|
+
text: "Uploading...",
|
|
8883
|
+
discardStdin: false
|
|
8884
|
+
}).start();
|
|
8885
|
+
const startTime = performance.now();
|
|
8886
|
+
let totalSize = 0;
|
|
8887
|
+
let amountUploaded = 0;
|
|
8888
|
+
const outputCommitments = {};
|
|
8889
|
+
let firstTransactionHash;
|
|
8890
|
+
for (const entry of filelist) {
|
|
8891
|
+
totalSize += entry.sizeBytes;
|
|
8892
|
+
}
|
|
8893
|
+
const isSingleFile = filelist.length === 1;
|
|
8894
|
+
const formatProgressPercent = () => totalSize > 0 ? (100 * (amountUploaded / totalSize)).toFixed(2) : "0.00";
|
|
8895
|
+
let filesProcessed = 0;
|
|
8896
|
+
for (const entry of filelist) {
|
|
8897
|
+
const fileName = path5.basename(entry.filename);
|
|
8898
|
+
const fileProgress = isSingleFile ? `(${formatProgressPercent()}%)` : `(${filesProcessed}/${filelist.length} files, ${formatProgressPercent()}%)`;
|
|
8899
|
+
spinner.text = `\u{1F4D6} Reading ${fileName}... ${fileProgress}`;
|
|
8900
|
+
try {
|
|
8901
|
+
const blobData = await fs5.readFile(entry.filename);
|
|
8902
|
+
if (blobData.length !== entry.sizeBytes) {
|
|
8903
|
+
throw new Error(
|
|
8904
|
+
`Size of file ${entry.filename} changed after initial scan. Original size was ${entry.sizeBytes} but it is now ${blobData.length}`
|
|
8905
|
+
);
|
|
8906
|
+
}
|
|
8907
|
+
spinner.text = `\u{1F517} Checking if blob exists... ${fileProgress}`;
|
|
8908
|
+
const existingBlobMetadata = await shelbyClient.coordination.getBlobMetadata({
|
|
8909
|
+
account: activeAccount.accountAddress,
|
|
8910
|
+
name: entry.blobname
|
|
8911
|
+
});
|
|
8912
|
+
if (existingBlobMetadata) {
|
|
8913
|
+
spinner.fail(
|
|
8914
|
+
`Blob '${entry.blobname}' already exists. Please use a different name or delete the existing blob first.`
|
|
8915
|
+
);
|
|
8916
|
+
process.exit(1);
|
|
8917
|
+
}
|
|
8918
|
+
spinner.text = `\u{1F517} Generating commitments for ${fileName}... ${fileProgress}`;
|
|
8919
|
+
const provider = await getErasureCodingProvider();
|
|
8920
|
+
const blobCommitments = await generateCommitments(provider, blobData);
|
|
8921
|
+
if (validatedOptions.outputCommitments) {
|
|
8922
|
+
outputCommitments[entry.filename] = blobCommitments;
|
|
8923
|
+
}
|
|
8924
|
+
spinner.text = `\u{1F517} Registering ${fileName} on L1... ${fileProgress}`;
|
|
8925
|
+
const { transaction: pendingRegisterBlobTransaction } = await shelbyClient.coordination.registerBlob({
|
|
8926
|
+
account: activeAccount,
|
|
8927
|
+
blobName: entry.blobname,
|
|
8928
|
+
blobMerkleRoot: blobCommitments.blob_merkle_root,
|
|
8929
|
+
size: blobData.length,
|
|
8930
|
+
expirationMicros: validatedOptions.expiration.getTime() * 1e3
|
|
8931
|
+
});
|
|
8932
|
+
const registerTransactionHash = pendingRegisterBlobTransaction.hash;
|
|
8933
|
+
if (!firstTransactionHash) {
|
|
8934
|
+
firstTransactionHash = registerTransactionHash;
|
|
8935
|
+
}
|
|
8936
|
+
await aptos.waitForTransaction({
|
|
8937
|
+
transactionHash: pendingRegisterBlobTransaction.hash
|
|
8938
|
+
});
|
|
8939
|
+
spinner.text = `\u{1F4E4} Uploading ${fileName} to Shelby RPC... ${fileProgress}`;
|
|
8940
|
+
await shelbyClient.rpc.putBlob({
|
|
8941
|
+
account: activeAccount.accountAddress,
|
|
8942
|
+
blobName: entry.blobname,
|
|
8943
|
+
blobData
|
|
8944
|
+
});
|
|
8945
|
+
amountUploaded += blobData.length;
|
|
8946
|
+
filesProcessed++;
|
|
8947
|
+
} catch (error) {
|
|
8948
|
+
const { displayMessage } = handleError(error);
|
|
8949
|
+
spinner.fail(displayMessage);
|
|
8950
|
+
process.exit(1);
|
|
8951
|
+
}
|
|
8952
|
+
}
|
|
8953
|
+
if (validatedOptions.outputCommitments) {
|
|
8954
|
+
await fs5.writeFile(
|
|
8955
|
+
validatedOptions.outputCommitments,
|
|
8956
|
+
JSON.stringify(outputCommitments)
|
|
8957
|
+
);
|
|
8958
|
+
}
|
|
8545
8959
|
const elapsedSec = ((performance.now() - startTime) / 1e3).toFixed(2);
|
|
8546
8960
|
spinner.succeed(`Upload complete \u2014 took ${elapsedSec}s`);
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8961
|
+
console.log("");
|
|
8962
|
+
if (firstTransactionHash) {
|
|
8963
|
+
const explorerUrl = getAptosTransactionExplorerUrl(
|
|
8964
|
+
shelbyConfig.network,
|
|
8965
|
+
firstTransactionHash
|
|
8966
|
+
);
|
|
8967
|
+
console.log("\u{1F310} Aptos Explorer:");
|
|
8968
|
+
console.log(` ${explorerUrl}`);
|
|
8969
|
+
console.log("");
|
|
8970
|
+
}
|
|
8971
|
+
const shelbyExplorerUrl = getShelbyAccountExplorerUrl(
|
|
8553
8972
|
shelbyConfig.network,
|
|
8554
|
-
|
|
8973
|
+
activeAccount.accountAddress.toString()
|
|
8555
8974
|
);
|
|
8556
|
-
console.log("\u{
|
|
8557
|
-
console.log(` ${
|
|
8975
|
+
console.log("\u{1F5C2}\uFE0F Shelby Explorer:");
|
|
8976
|
+
console.log(` ${shelbyExplorerUrl}`);
|
|
8977
|
+
console.log("");
|
|
8978
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8979
|
+
console.log("\u2728 Done!");
|
|
8558
8980
|
console.log("");
|
|
8559
8981
|
}
|
|
8560
|
-
|
|
8561
|
-
shelbyConfig.network,
|
|
8562
|
-
activeAccount.accountAddress.toString()
|
|
8563
|
-
);
|
|
8564
|
-
console.log("\u{1F5C2}\uFE0F Shelby Explorer:");
|
|
8565
|
-
console.log(` ${shelbyExplorerUrl}`);
|
|
8566
|
-
console.log("");
|
|
8567
|
-
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8568
|
-
console.log("\u2728 Done!");
|
|
8569
|
-
console.log("");
|
|
8570
|
-
});
|
|
8982
|
+
);
|
|
8571
8983
|
}
|
|
8572
8984
|
|
|
8573
8985
|
// src/cli.tsx
|