@solana-mobile/dapp-store-cli 0.4.2 → 0.5.0

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.
Files changed (112) hide show
  1. package/bin/dapp-store.js +1 -1
  2. package/lib/CliSetup.js +730 -0
  3. package/lib/CliUtils.js +309 -0
  4. package/lib/__tests__/CliSetupTest.js +140 -0
  5. package/lib/commands/ValidateCommand.js +201 -0
  6. package/lib/commands/create/CreateCliApp.js +240 -0
  7. package/lib/commands/create/CreateCliPublisher.js +238 -0
  8. package/lib/commands/create/CreateCliRelease.js +270 -0
  9. package/lib/commands/create/index.js +41 -0
  10. package/lib/{esm/commands → commands}/index.js +0 -1
  11. package/lib/commands/publish/PublishCliRemove.js +186 -0
  12. package/lib/commands/publish/PublishCliSubmit.js +192 -0
  13. package/lib/commands/publish/PublishCliSupport.js +186 -0
  14. package/lib/commands/publish/PublishCliUpdate.js +193 -0
  15. package/lib/commands/publish/index.js +22 -0
  16. package/lib/{esm/commands → commands}/scaffolding/ScaffoldInit.js +7 -6
  17. package/lib/{esm/commands → commands}/scaffolding/index.js +0 -1
  18. package/lib/config/EnvVariables.js +59 -0
  19. package/lib/config/PublishDetails.js +591 -0
  20. package/lib/config/S3StorageManager.js +93 -0
  21. package/lib/config/index.js +2 -0
  22. package/lib/generated/config_obj.json +1 -0
  23. package/lib/generated/config_schema.json +1 -0
  24. package/lib/index.js +148 -0
  25. package/lib/package.json +74 -0
  26. package/lib/prebuild_schema/publishing_source.yaml +46 -0
  27. package/lib/prebuild_schema/schemagen.js +20 -0
  28. package/lib/upload/CachedStorageDriver.js +307 -0
  29. package/lib/{esm/upload → upload}/index.js +0 -1
  30. package/package.json +21 -7
  31. package/src/CliSetup.ts +511 -0
  32. package/src/CliUtils.ts +64 -19
  33. package/src/__tests__/CliSetupTest.ts +212 -0
  34. package/src/commands/create/CreateCliApp.ts +6 -1
  35. package/src/commands/create/CreateCliPublisher.ts +6 -1
  36. package/src/commands/create/CreateCliRelease.ts +6 -1
  37. package/src/config/EnvVariables.ts +39 -0
  38. package/src/config/PublishDetails.ts +16 -2
  39. package/src/config/S3StorageManager.ts +47 -0
  40. package/src/config/index.ts +2 -0
  41. package/src/index.ts +3 -510
  42. package/lib/esm/CliUtils.js +0 -108
  43. package/lib/esm/CliUtils.js.map +0 -1
  44. package/lib/esm/commands/ValidateCommand.js +0 -42
  45. package/lib/esm/commands/ValidateCommand.js.map +0 -1
  46. package/lib/esm/commands/create/CreateCliApp.js +0 -41
  47. package/lib/esm/commands/create/CreateCliApp.js.map +0 -1
  48. package/lib/esm/commands/create/CreateCliPublisher.js +0 -36
  49. package/lib/esm/commands/create/CreateCliPublisher.js.map +0 -1
  50. package/lib/esm/commands/create/CreateCliRelease.js +0 -50
  51. package/lib/esm/commands/create/CreateCliRelease.js.map +0 -1
  52. package/lib/esm/commands/create/index.js +0 -44
  53. package/lib/esm/commands/create/index.js.map +0 -1
  54. package/lib/esm/commands/index.js.map +0 -1
  55. package/lib/esm/commands/publish/PublishCliRemove.js +0 -26
  56. package/lib/esm/commands/publish/PublishCliRemove.js.map +0 -1
  57. package/lib/esm/commands/publish/PublishCliSubmit.js +0 -31
  58. package/lib/esm/commands/publish/PublishCliSubmit.js.map +0 -1
  59. package/lib/esm/commands/publish/PublishCliSupport.js +0 -26
  60. package/lib/esm/commands/publish/PublishCliSupport.js.map +0 -1
  61. package/lib/esm/commands/publish/PublishCliUpdate.js +0 -32
  62. package/lib/esm/commands/publish/PublishCliUpdate.js.map +0 -1
  63. package/lib/esm/commands/publish/index.js +0 -25
  64. package/lib/esm/commands/publish/index.js.map +0 -1
  65. package/lib/esm/commands/scaffolding/ScaffoldInit.js.map +0 -1
  66. package/lib/esm/commands/scaffolding/index.js.map +0 -1
  67. package/lib/esm/config/PublishDetails.js +0 -177
  68. package/lib/esm/config/PublishDetails.js.map +0 -1
  69. package/lib/esm/generated/config_obj.json +0 -1
  70. package/lib/esm/generated/config_schema.json +0 -1
  71. package/lib/esm/index.js +0 -307
  72. package/lib/esm/index.js.map +0 -1
  73. package/lib/esm/package.json +0 -60
  74. package/lib/esm/upload/CachedStorageDriver.js +0 -66
  75. package/lib/esm/upload/CachedStorageDriver.js.map +0 -1
  76. package/lib/esm/upload/index.js.map +0 -1
  77. package/lib/types/CliUtils.d.ts +0 -21
  78. package/lib/types/CliUtils.d.ts.map +0 -1
  79. package/lib/types/commands/ValidateCommand.d.ts +0 -6
  80. package/lib/types/commands/ValidateCommand.d.ts.map +0 -1
  81. package/lib/types/commands/create/CreateCliApp.d.ts +0 -12
  82. package/lib/types/commands/create/CreateCliApp.d.ts.map +0 -1
  83. package/lib/types/commands/create/CreateCliPublisher.d.ts +0 -9
  84. package/lib/types/commands/create/CreateCliPublisher.d.ts.map +0 -1
  85. package/lib/types/commands/create/CreateCliRelease.d.ts +0 -13
  86. package/lib/types/commands/create/CreateCliRelease.d.ts.map +0 -1
  87. package/lib/types/commands/create/index.d.ts +0 -4
  88. package/lib/types/commands/create/index.d.ts.map +0 -1
  89. package/lib/types/commands/index.d.ts +0 -4
  90. package/lib/types/commands/index.d.ts.map +0 -1
  91. package/lib/types/commands/publish/PublishCliRemove.d.ts +0 -13
  92. package/lib/types/commands/publish/PublishCliRemove.d.ts.map +0 -1
  93. package/lib/types/commands/publish/PublishCliSubmit.d.ts +0 -13
  94. package/lib/types/commands/publish/PublishCliSubmit.d.ts.map +0 -1
  95. package/lib/types/commands/publish/PublishCliSupport.d.ts +0 -13
  96. package/lib/types/commands/publish/PublishCliSupport.d.ts.map +0 -1
  97. package/lib/types/commands/publish/PublishCliUpdate.d.ts +0 -14
  98. package/lib/types/commands/publish/PublishCliUpdate.d.ts.map +0 -1
  99. package/lib/types/commands/publish/index.d.ts +0 -5
  100. package/lib/types/commands/publish/index.d.ts.map +0 -1
  101. package/lib/types/commands/scaffolding/ScaffoldInit.d.ts +0 -2
  102. package/lib/types/commands/scaffolding/ScaffoldInit.d.ts.map +0 -1
  103. package/lib/types/commands/scaffolding/index.d.ts +0 -2
  104. package/lib/types/commands/scaffolding/index.d.ts.map +0 -1
  105. package/lib/types/config/PublishDetails.d.ts +0 -17
  106. package/lib/types/config/PublishDetails.d.ts.map +0 -1
  107. package/lib/types/index.d.ts +0 -2
  108. package/lib/types/index.d.ts.map +0 -1
  109. package/lib/types/upload/CachedStorageDriver.d.ts +0 -30
  110. package/lib/types/upload/CachedStorageDriver.d.ts.map +0 -1
  111. package/lib/types/upload/index.d.ts +0 -2
  112. package/lib/types/upload/index.d.ts.map +0 -1
@@ -0,0 +1,511 @@
1
+ import { Command } from "commander";
2
+ import { validateCommand } from "./commands/index.js";
3
+ import { createAppCommand, createPublisherCommand, createReleaseCommand } from "./commands/create/index.js";
4
+ import {
5
+ publishRemoveCommand,
6
+ publishSubmitCommand,
7
+ publishSupportCommand,
8
+ publishUpdateCommand
9
+ } from "./commands/publish/index.js";
10
+ import {
11
+ checkForSelfUpdate,
12
+ checkSubmissionNetwork,
13
+ Constants,
14
+ generateNetworkSuffix,
15
+ parseKeypair,
16
+ showMessage
17
+ } from "./CliUtils.js";
18
+ import * as dotenv from "dotenv";
19
+ import { initScaffold } from "./commands/scaffolding/index.js";
20
+ import { loadPublishDetails, loadPublishDetailsWithChecks } from "./config/PublishDetails.js";
21
+
22
+ dotenv.config();
23
+
24
+ const hasAddressInConfig = ({ address }: { address: string }) => {
25
+ return !!address;
26
+ };
27
+
28
+ export const mainCli = new Command();
29
+
30
+ function resolveBuildToolsPath(buildToolsPath: string | undefined) {
31
+ // If a path was specified on the command line, use that
32
+ if (buildToolsPath !== undefined) {
33
+ return buildToolsPath;
34
+ }
35
+
36
+ // If a path is specified in a .env file, use that
37
+ if (process.env.ANDROID_TOOLS_DIR !== undefined) {
38
+ return process.env.ANDROID_TOOLS_DIR;
39
+ }
40
+
41
+ // No path was specified
42
+ return;
43
+ }
44
+
45
+ /**
46
+ * This method should be updated with each new release of the CLI, and just do nothing when there isn't anything to report
47
+ */
48
+ function latestReleaseMessage() {
49
+ showMessage(
50
+ `Publishing Tools Version ${ Constants.CLI_VERSION }`,
51
+ "- short_description value reduced to 30 character limit",
52
+ "warning"
53
+ );
54
+ }
55
+
56
+ async function tryWithErrorMessage(block: () => Promise<any>) {
57
+ try {
58
+ await block()
59
+ } catch (e) {
60
+ const errorMsg = (e as Error | null)?.message ?? "";
61
+
62
+ showMessage("Error", errorMsg, "error");
63
+ }
64
+ }
65
+
66
+ mainCli
67
+ .name("dapp-store")
68
+ .version(Constants.CLI_VERSION)
69
+ .description("CLI to assist with publishing to the Saga Dapp Store")
70
+
71
+ export const initCliCmd = mainCli
72
+ .command("init")
73
+ .description("First-time initialization of tooling configuration")
74
+ .action(async () => {
75
+ tryWithErrorMessage(async () => {
76
+ const msg = initScaffold();
77
+
78
+ showMessage("Initialized", msg);
79
+ })
80
+ });
81
+
82
+ export const createCliCmd = mainCli
83
+ .command("create")
84
+ .description("Create a `publisher`, `app`, or `release`")
85
+
86
+ export const createPublisherCliCmd = createCliCmd
87
+ .command("publisher")
88
+ .description("Create a publisher")
89
+ .requiredOption(
90
+ "-k, --keypair <path-to-keypair-file>",
91
+ "Path to keypair file"
92
+ )
93
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
94
+ .option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
95
+ .option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
96
+ .action(async ({ keypair, url, dryRun, storageConfig }) => {
97
+ tryWithErrorMessage(async () => {
98
+ latestReleaseMessage();
99
+ await checkForSelfUpdate();
100
+
101
+ const signer = parseKeypair(keypair);
102
+ if (signer) {
103
+ const result: { publisherAddress: string } = await createPublisherCommand({ signer, url, dryRun, storageParams: storageConfig });
104
+
105
+ const displayUrl = `https://solscan.io/token/${result.publisherAddress}${generateNetworkSuffix(url)}`;
106
+ const resultText = `Publisher NFT successfully minted:\n${displayUrl}`;
107
+
108
+ showMessage("Success", resultText);
109
+ }
110
+ });
111
+ });
112
+
113
+ export const createAppCliCmd = createCliCmd
114
+ .command("app")
115
+ .description("Create a app")
116
+ .requiredOption(
117
+ "-k, --keypair <path-to-keypair-file>",
118
+ "Path to keypair file"
119
+ )
120
+ .option(
121
+ "-p, --publisher-mint-address <publisher-mint-address>",
122
+ "The mint address of the publisher NFT"
123
+ )
124
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
125
+ .option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
126
+ .option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
127
+ .action(async ({ publisherMintAddress, keypair, url, dryRun, storageConfig }) => {
128
+ tryWithErrorMessage(async () => {
129
+ latestReleaseMessage();
130
+ await checkForSelfUpdate();
131
+
132
+ const config = await loadPublishDetailsWithChecks();
133
+
134
+ if (!hasAddressInConfig(config.publisher) && !publisherMintAddress) {
135
+ throw new Error("Either specify a publisher mint address in the config file or specify as a CLI argument to this command.");
136
+ }
137
+
138
+ const signer = parseKeypair(keypair);
139
+ if (signer) {
140
+ const result = await createAppCommand({
141
+ publisherMintAddress: publisherMintAddress,
142
+ signer,
143
+ url,
144
+ dryRun,
145
+ storageParams: storageConfig
146
+ });
147
+
148
+ const displayUrl = `https://solscan.io/token/${result.appAddress}${generateNetworkSuffix(url)}`;
149
+ const resultText = `App NFT successfully minted:\n${displayUrl}`;
150
+
151
+ showMessage("Success", resultText);
152
+ }
153
+ });
154
+ });
155
+
156
+ export const createReleaseCliCmd = createCliCmd
157
+ .command("release")
158
+ .description("Create a release")
159
+ .requiredOption(
160
+ "-k, --keypair <path-to-keypair-file>",
161
+ "Path to keypair file"
162
+ )
163
+ .option(
164
+ "-a, --app-mint-address <app-mint-address>",
165
+ "The mint address of the app NFT"
166
+ )
167
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
168
+ .option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
169
+ .option(
170
+ "-b, --build-tools-path <build-tools-path>",
171
+ "Path to Android build tools which contains AAPT2"
172
+ )
173
+ .option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
174
+ .action(async ({ appMintAddress, keypair, url, dryRun, buildToolsPath, storageConfig }) => {
175
+ tryWithErrorMessage(async () => {
176
+ latestReleaseMessage();
177
+ await checkForSelfUpdate();
178
+
179
+ const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
180
+ if (resolvedBuildToolsPath === undefined) {
181
+ throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
182
+ }
183
+
184
+ const config = await loadPublishDetailsWithChecks();
185
+ if (!hasAddressInConfig(config.app) && !appMintAddress) {
186
+ throw new Error("Either specify an app mint address in the config file or specify as a CLI argument to this command")
187
+ }
188
+
189
+ const signer = parseKeypair(keypair);
190
+ if (signer) {
191
+ const result = await createReleaseCommand({
192
+ appMintAddress: appMintAddress,
193
+ buildToolsPath: resolvedBuildToolsPath,
194
+ signer,
195
+ url,
196
+ dryRun,
197
+ storageParams: storageConfig,
198
+ });
199
+
200
+ const displayUrl = `https://solscan.io/token/${result?.releaseAddress}${generateNetworkSuffix(url)}`;
201
+ const resultText = `Release NFT successfully minted:\n${displayUrl}`;
202
+
203
+ showMessage("Success", resultText);
204
+ }
205
+ });
206
+ }
207
+ );
208
+
209
+ mainCli
210
+ .command("validate")
211
+ .description("Validates details prior to publishing")
212
+ .requiredOption(
213
+ "-k, --keypair <path-to-keypair-file>",
214
+ "Path to keypair file"
215
+ )
216
+ .option(
217
+ "-b, --build-tools-path <build-tools-path>",
218
+ "Path to Android build tools which contains AAPT2"
219
+ )
220
+ .action(async ({ keypair, buildToolsPath }) => {
221
+ tryWithErrorMessage(async () => {
222
+ latestReleaseMessage();
223
+ await checkForSelfUpdate();
224
+
225
+ const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
226
+ if (resolvedBuildToolsPath === undefined) {
227
+ throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
228
+ }
229
+
230
+ const signer = parseKeypair(keypair);
231
+ if (signer) {
232
+ await validateCommand({
233
+ signer,
234
+ buildToolsPath: resolvedBuildToolsPath,
235
+ });
236
+
237
+ //TODO: Add pretty formatting here, but will require more work than other sections
238
+ }
239
+ });
240
+ });
241
+
242
+ const publishCommand = mainCli
243
+ .command("publish")
244
+ .description(
245
+ "Submit a publishing request (`submit`, `update`, `remove`, or `support`) to the Solana Mobile dApp publisher portal"
246
+ );
247
+
248
+ publishCommand
249
+ .command("submit")
250
+ .description("Submit a new app to the Solana Mobile dApp publisher portal")
251
+ .requiredOption(
252
+ "-k, --keypair <path-to-keypair-file>",
253
+ "Path to keypair file"
254
+ )
255
+ .requiredOption(
256
+ "--complies-with-solana-dapp-store-policies",
257
+ "An attestation that the app complies with the Solana dApp Store policies"
258
+ )
259
+ .requiredOption(
260
+ "--requestor-is-authorized",
261
+ "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
262
+ )
263
+ .option(
264
+ "-a, --app-mint-address <app-mint-address>",
265
+ "The mint address of the app NFT. If not specified, the value from your config file will be used."
266
+ )
267
+ .option(
268
+ "-r, --release-mint-address <release-mint-address>",
269
+ "The mint address of the release NFT. If not specified, the value from your config file will be used."
270
+ )
271
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
272
+ .option(
273
+ "-d, --dry-run",
274
+ "Flag for dry run. Doesn't submit the request to the publisher portal."
275
+ )
276
+ .action(
277
+ async ({
278
+ appMintAddress,
279
+ releaseMintAddress,
280
+ keypair,
281
+ url,
282
+ compliesWithSolanaDappStorePolicies,
283
+ requestorIsAuthorized,
284
+ dryRun,
285
+ }) => {
286
+ tryWithErrorMessage(async () => {
287
+ await checkForSelfUpdate();
288
+ await checkSubmissionNetwork(url);
289
+
290
+ const config = await loadPublishDetails(Constants.getConfigFilePath());
291
+
292
+ if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
293
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
294
+ }
295
+
296
+ const signer = parseKeypair(keypair);
297
+ if (signer) {
298
+ await publishSubmitCommand({
299
+ appMintAddress,
300
+ releaseMintAddress,
301
+ signer,
302
+ url,
303
+ dryRun,
304
+ compliesWithSolanaDappStorePolicies,
305
+ requestorIsAuthorized,
306
+ });
307
+
308
+ const resultText = "Successfully submitted to the Solana Mobile dApp publisher portal";
309
+ showMessage("Success", resultText);
310
+ }
311
+ });
312
+ }
313
+ );
314
+
315
+ publishCommand
316
+ .command("update")
317
+ .description(
318
+ "Update an existing app on the Solana Mobile dApp publisher portal"
319
+ )
320
+ .requiredOption(
321
+ "-k, --keypair <path-to-keypair-file>",
322
+ "Path to keypair file"
323
+ )
324
+ .requiredOption(
325
+ "--complies-with-solana-dapp-store-policies",
326
+ "An attestation that the app complies with the Solana dApp Store policies"
327
+ )
328
+ .requiredOption(
329
+ "--requestor-is-authorized",
330
+ "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
331
+ )
332
+ .option(
333
+ "-a, --app-mint-address <app-mint-address>",
334
+ "The mint address of the app NFT. If not specified, the value from your config file will be used."
335
+ )
336
+ .option(
337
+ "-r, --release-mint-address <release-mint-address>",
338
+ "The mint address of the release NFT. If not specified, the value from your config file will be used."
339
+ )
340
+ .option("-c, --critical", "Flag for a critical app update request")
341
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
342
+ .option(
343
+ "-d, --dry-run",
344
+ "Flag for dry run. Doesn't submit the request to the publisher portal."
345
+ )
346
+ .action(
347
+ async ({
348
+ appMintAddress,
349
+ releaseMintAddress,
350
+ keypair,
351
+ url,
352
+ compliesWithSolanaDappStorePolicies,
353
+ requestorIsAuthorized,
354
+ critical,
355
+ dryRun,
356
+ }) => {
357
+ tryWithErrorMessage(async () => {
358
+ await checkForSelfUpdate();
359
+ await checkSubmissionNetwork(url);
360
+
361
+ const config = await loadPublishDetails(Constants.getConfigFilePath())
362
+
363
+ if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
364
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
365
+ }
366
+
367
+ const signer = parseKeypair(keypair);
368
+ if (signer) {
369
+ await publishUpdateCommand({
370
+ appMintAddress,
371
+ releaseMintAddress,
372
+ signer,
373
+ url,
374
+ dryRun,
375
+ compliesWithSolanaDappStorePolicies,
376
+ requestorIsAuthorized,
377
+ critical,
378
+ });
379
+
380
+ const resultText = "dApp successfully updated on the publisher portal";
381
+ showMessage("Success", resultText);
382
+ }
383
+ });
384
+ }
385
+ );
386
+
387
+ publishCommand
388
+ .command("remove")
389
+ .description(
390
+ "Remove an existing app from the Solana Mobile dApp publisher portal"
391
+ )
392
+ .requiredOption(
393
+ "-k, --keypair <path-to-keypair-file>",
394
+ "Path to keypair file"
395
+ )
396
+ .requiredOption(
397
+ "--requestor-is-authorized",
398
+ "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
399
+ )
400
+ .option(
401
+ "-a, --app-mint-address <app-mint-address>",
402
+ "The mint address of the app NFT. If not specified, the value from your config file will be used."
403
+ )
404
+ .option(
405
+ "-r, --release-mint-address <release-mint-address>",
406
+ "The mint address of the release NFT. If not specified, the value from your config file will be used."
407
+ )
408
+ .option("-c, --critical", "Flag for a critical app removal request")
409
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
410
+ .option(
411
+ "-d, --dry-run",
412
+ "Flag for dry run. Doesn't submit the request to the publisher portal."
413
+ )
414
+ .action(
415
+ async ({
416
+ appMintAddress,
417
+ releaseMintAddress,
418
+ keypair,
419
+ url,
420
+ requestorIsAuthorized,
421
+ critical,
422
+ dryRun,
423
+ }) => {
424
+ tryWithErrorMessage(async () => {
425
+ await checkForSelfUpdate();
426
+ await checkSubmissionNetwork(url);
427
+
428
+ const config = await loadPublishDetails(Constants.getConfigFilePath())
429
+
430
+ if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
431
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
432
+ }
433
+
434
+ const signer = parseKeypair(keypair);
435
+ if (signer) {
436
+ await publishRemoveCommand({
437
+ appMintAddress,
438
+ releaseMintAddress,
439
+ signer,
440
+ url,
441
+ dryRun,
442
+ requestorIsAuthorized,
443
+ critical,
444
+ });
445
+
446
+ const resultText = "dApp successfully removed from the publisher portal";
447
+ showMessage("Success", resultText);
448
+ }
449
+ })
450
+ }
451
+ );
452
+
453
+ publishCommand
454
+ .command("support <request_details>")
455
+ .description(
456
+ "Submit a support request for an existing app on the Solana Mobile dApp publisher portal"
457
+ )
458
+ .requiredOption(
459
+ "-k, --keypair <path-to-keypair-file>",
460
+ "Path to keypair file"
461
+ )
462
+ .requiredOption(
463
+ "--requestor-is-authorized",
464
+ "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
465
+ )
466
+ .option(
467
+ "-a, --app-mint-address <app-mint-address>",
468
+ "The mint address of the app NFT. If not specified, the value from your config file will be used."
469
+ )
470
+ .option(
471
+ "-r, --release-mint-address <release-mint-address>",
472
+ "The mint address of the release NFT. If not specified, the value from your config file will be used."
473
+ )
474
+ .option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
475
+ .option(
476
+ "-d, --dry-run",
477
+ "Flag for dry run. Doesn't submit the request to the publisher portal."
478
+ )
479
+ .action(
480
+ async (
481
+ requestDetails,
482
+ { appMintAddress, releaseMintAddress, keypair, url, requestorIsAuthorized, dryRun }
483
+ ) => {
484
+ tryWithErrorMessage(async () => {
485
+ await checkForSelfUpdate();
486
+ await checkSubmissionNetwork(url);
487
+
488
+ const config = await loadPublishDetails(Constants.getConfigFilePath())
489
+
490
+ if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
491
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
492
+ }
493
+
494
+ const signer = parseKeypair(keypair);
495
+ if (signer) {
496
+ await publishSupportCommand({
497
+ appMintAddress,
498
+ releaseMintAddress,
499
+ signer,
500
+ url,
501
+ dryRun,
502
+ requestorIsAuthorized,
503
+ requestDetails,
504
+ });
505
+
506
+ const resultText = "Support request sent successfully";
507
+ showMessage("Success", resultText);
508
+ }
509
+ });
510
+ }
511
+ );
package/src/CliUtils.ts CHANGED
@@ -2,15 +2,23 @@ import fs from "fs";
2
2
  import type { Connection } from "@solana/web3.js";
3
3
  import { Keypair, PublicKey } from "@solana/web3.js";
4
4
  import debugModule from "debug";
5
- import { BundlrStorageDriver, keypairIdentity, Metaplex } from "@metaplex-foundation/js";
5
+ import {
6
+ BundlrStorageDriver,
7
+ keypairIdentity,
8
+ Metaplex,
9
+ } from "@metaplex-foundation/js";
6
10
  import updateNotifier from "update-notifier";
7
11
  import cliPackage from "./package.json" assert { type: "json" };
8
12
  import boxen from "boxen";
9
13
  import ver from "semver";
10
14
  import { CachedStorageDriver } from "./upload/CachedStorageDriver.js";
15
+ import { EnvVariables } from "./config/index.js";
16
+ import { S3Client } from "@aws-sdk/client-s3";
17
+ import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
18
+ import { S3StorageManager } from "./config/index.js";
11
19
 
12
20
  export class Constants {
13
- static CLI_VERSION = "0.4.2";
21
+ static CLI_VERSION = "0.5.0";
14
22
  static CONFIG_FILE_NAME = "config.yaml";
15
23
  static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
16
24
 
@@ -28,21 +36,35 @@ export const checkForSelfUpdate = async () => {
28
36
  const latestVer = new ver.SemVer(updateInfo.latest);
29
37
  const currentVer = new ver.SemVer(updateInfo.current);
30
38
 
31
- if (latestVer.major > currentVer.major || latestVer.minor > currentVer.minor) {
32
- throw new Error("Please update to the latest version of the dApp Store CLI before proceeding.");
39
+ if (
40
+ latestVer.major > currentVer.major ||
41
+ latestVer.minor > currentVer.minor
42
+ ) {
43
+ throw new Error(
44
+ "Please update to the latest version of the dApp Store CLI before proceeding."
45
+ );
33
46
  }
34
47
  };
35
48
 
36
- export const checkMintedStatus = async (conn: Connection, pubAddr: string, appAddr: string, releaseAddr: string) => {
49
+ export const checkMintedStatus = async (
50
+ conn: Connection,
51
+ pubAddr: string,
52
+ appAddr: string,
53
+ releaseAddr: string
54
+ ) => {
37
55
  const results = await conn.getMultipleAccountsInfo([
38
56
  new PublicKey(pubAddr),
39
57
  new PublicKey(appAddr),
40
58
  new PublicKey(releaseAddr),
41
59
  ]);
42
60
 
43
- const rentAccounts = results.filter((item) => !(item == undefined) && item?.lamports > 0);
61
+ const rentAccounts = results.filter(
62
+ (item) => !(item == undefined) && item?.lamports > 0
63
+ );
44
64
  if (rentAccounts?.length != 3) {
45
- throw new Error("Please ensure you have minted all of your NFTs before submitting to the Solana Mobile dApp publisher portal.");
65
+ throw new Error(
66
+ "Please ensure you have minted all of your NFTs before submitting to the Solana Mobile dApp publisher portal."
67
+ );
46
68
  }
47
69
  };
48
70
 
@@ -51,12 +73,12 @@ export const parseKeypair = (pathToKeypairFile: string) => {
51
73
  const keypairFile = fs.readFileSync(pathToKeypairFile, "utf-8");
52
74
  return Keypair.fromSecretKey(Buffer.from(JSON.parse(keypairFile)));
53
75
  } catch (e) {
54
- showMessage
55
- (
76
+ showMessage(
56
77
  "KeyPair Error",
57
- "Something went wrong when attempting to retrieve the keypair at " + pathToKeypairFile,
78
+ "Something went wrong when attempting to retrieve the keypair at " +
79
+ pathToKeypairFile,
58
80
  "error"
59
- )
81
+ );
60
82
  }
61
83
  };
62
84
 
@@ -70,7 +92,9 @@ export const isTestnet = (rpcUrl: string): boolean => {
70
92
 
71
93
  export const checkSubmissionNetwork = (rpcUrl: string) => {
72
94
  if (isDevnet(rpcUrl) || isTestnet(rpcUrl)) {
73
- throw new Error("It looks like you are attempting to submit a request with a devnet or testnet RPC endpoint. Please ensure that your NFTs are minted on mainnet beta, and re-run with a mainnet beta RPC endpoint.");
95
+ throw new Error(
96
+ "It looks like you are attempting to submit a request with a devnet or testnet RPC endpoint. Please ensure that your NFTs are minted on mainnet beta, and re-run with a mainnet beta RPC endpoint."
97
+ );
74
98
  }
75
99
  };
76
100
 
@@ -91,7 +115,7 @@ export const generateNetworkSuffix = (rpcUrl: string): string => {
91
115
  export const showMessage = (
92
116
  titleMessage = "",
93
117
  contentMessage = "",
94
- type: "standard" | "error" | "warning" = "standard",
118
+ type: "standard" | "error" | "warning" = "standard"
95
119
  ): string => {
96
120
  let color = "cyan";
97
121
  if (type == "error") {
@@ -104,7 +128,7 @@ export const showMessage = (
104
128
  title: titleMessage,
105
129
  padding: 1,
106
130
  margin: 1,
107
- borderStyle: 'single',
131
+ borderStyle: "single",
108
132
  borderColor: color,
109
133
  textAlignment: "left",
110
134
  titleAlignment: "center",
@@ -116,24 +140,45 @@ export const showMessage = (
116
140
 
117
141
  export const getMetaplexInstance = (
118
142
  connection: Connection,
119
- keypair: Keypair
143
+ keypair: Keypair,
144
+ storageParams: string = ""
120
145
  ) => {
121
146
  const metaplex = Metaplex.make(connection).use(keypairIdentity(keypair));
122
147
  const isDevnet = connection.rpcEndpoint.includes("devnet");
123
148
 
124
- const bundlrStorageDriver = isDevnet
125
- ? new BundlrStorageDriver(metaplex, {
149
+ //TODO: Use DI for this
150
+ const s3Mgr = new S3StorageManager(new EnvVariables());
151
+ s3Mgr.parseCmdArg(storageParams);
152
+
153
+ if (s3Mgr.hasS3Config) {
154
+ const awsClient = new S3Client({
155
+ region: s3Mgr.s3Config.regionName,
156
+ credentials: {
157
+ accessKeyId: s3Mgr.s3Config.accessKey,
158
+ secretAccessKey: s3Mgr.s3Config.secretKey,
159
+ },
160
+ });
161
+
162
+ const bucketPlugin = awsStorage(awsClient, s3Mgr.s3Config.bucketName);
163
+ metaplex.use(bucketPlugin);
164
+ } else {
165
+ const bundlrStorageDriver = isDevnet
166
+ ? new BundlrStorageDriver(metaplex, {
126
167
  address: "https://devnet.bundlr.network",
127
168
  providerUrl: Constants.DEFAULT_RPC_DEVNET,
128
169
  })
129
- : new BundlrStorageDriver(metaplex);
170
+ : new BundlrStorageDriver(metaplex);
171
+
172
+ metaplex.storage().setDriver(bundlrStorageDriver);
173
+ }
130
174
 
131
175
  metaplex.storage().setDriver(
132
- new CachedStorageDriver(bundlrStorageDriver, {
176
+ new CachedStorageDriver(metaplex.storage().driver(), {
133
177
  assetManifestPath: isDevnet
134
178
  ? "./.asset-manifest-devnet.json"
135
179
  : "./.asset-manifest.json",
136
180
  })
137
181
  );
182
+
138
183
  return metaplex;
139
184
  };