@solana-mobile/dapp-store-cli 0.1.6 → 0.1.8

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 (40) hide show
  1. package/README.md +1 -5
  2. package/lib/esm/commands/create/app.js +3 -2
  3. package/lib/esm/commands/create/app.js.map +1 -1
  4. package/lib/esm/commands/publish/remove.js +3 -2
  5. package/lib/esm/commands/publish/remove.js.map +1 -1
  6. package/lib/esm/commands/publish/submit.js +3 -2
  7. package/lib/esm/commands/publish/submit.js.map +1 -1
  8. package/lib/esm/commands/publish/support.js +3 -2
  9. package/lib/esm/commands/publish/support.js.map +1 -1
  10. package/lib/esm/commands/publish/update.js +3 -2
  11. package/lib/esm/commands/publish/update.js.map +1 -1
  12. package/lib/esm/config/index.js +1 -1
  13. package/lib/esm/config/index.js.map +1 -1
  14. package/lib/esm/config/schema.json +0 -4
  15. package/lib/esm/index.js +92 -79
  16. package/lib/esm/index.js.map +1 -1
  17. package/lib/esm/package.json +57 -0
  18. package/lib/esm/utils.js +34 -6
  19. package/lib/esm/utils.js.map +1 -1
  20. package/lib/types/commands/create/app.d.ts.map +1 -1
  21. package/lib/types/commands/publish/remove.d.ts +2 -1
  22. package/lib/types/commands/publish/remove.d.ts.map +1 -1
  23. package/lib/types/commands/publish/submit.d.ts +2 -1
  24. package/lib/types/commands/publish/submit.d.ts.map +1 -1
  25. package/lib/types/commands/publish/support.d.ts +2 -1
  26. package/lib/types/commands/publish/support.d.ts.map +1 -1
  27. package/lib/types/commands/publish/update.d.ts +2 -1
  28. package/lib/types/commands/publish/update.d.ts.map +1 -1
  29. package/lib/types/utils.d.ts +3 -1
  30. package/lib/types/utils.d.ts.map +1 -1
  31. package/package.json +11 -3
  32. package/src/commands/create/app.ts +3 -2
  33. package/src/commands/publish/remove.ts +4 -0
  34. package/src/commands/publish/submit.ts +4 -0
  35. package/src/commands/publish/support.ts +4 -0
  36. package/src/commands/publish/update.ts +4 -0
  37. package/src/config/index.ts +1 -1
  38. package/src/config/schema.json +0 -4
  39. package/src/index.ts +124 -91
  40. package/src/utils.ts +41 -7
package/src/index.ts CHANGED
@@ -7,7 +7,9 @@ import {
7
7
  publishSupportCommand,
8
8
  publishUpdateCommand
9
9
  } from "./commands/publish/index.js";
10
- import { getConfigFile, parseKeypair, showUserErrorMessage } from "./utils.js";
10
+ import { checkForSelfUpdate, generateNetworkSuffix, getConfigFile, parseKeypair, showMessage } from "./utils.js";
11
+ import terminalLink from "terminal-link";
12
+ import boxen from "boxen";
11
13
 
12
14
  import * as dotenv from "dotenv";
13
15
 
@@ -34,10 +36,20 @@ function resolveBuildToolsPath(buildToolsPath: string | undefined) {
34
36
  return;
35
37
  }
36
38
 
39
+ async function tryWithErrorMessage(block: () => Promise<any>) {
40
+ try {
41
+ await block()
42
+ } catch (e) {
43
+ const errorMsg = (e as Error | null)?.message ?? "";
44
+
45
+ showMessage("Error", errorMsg, true);
46
+ }
47
+ }
48
+
37
49
  async function main() {
38
50
  program
39
51
  .name("dapp-store")
40
- .version("0.1.6")
52
+ .version("0.1.8")
41
53
  .description("CLI to assist with publishing to the Saga Dapp Store");
42
54
 
43
55
  const createCommand = program
@@ -54,11 +66,19 @@ async function main() {
54
66
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
55
67
  .option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
56
68
  .action(async ({ keypair, url, dryRun }) => {
57
- const signer = parseKeypair(keypair);
69
+ tryWithErrorMessage(async () => {
70
+ await checkForSelfUpdate();
58
71
 
59
- if (signer) {
60
- const result = await createPublisherCommand({ signer, url, dryRun });
61
- }
72
+ const signer = parseKeypair(keypair);
73
+ if (signer) {
74
+ const result: { publisherAddress: string } = await createPublisherCommand({ signer, url, dryRun });
75
+
76
+ const displayUrl = `https://solscan.io/token/${result.publisherAddress}${generateNetworkSuffix(url)}`;
77
+ const resultText = `Publisher NFT successfully minted:\n${displayUrl}`;
78
+
79
+ showMessage("Success", resultText);
80
+ }
81
+ });
62
82
  });
63
83
 
64
84
  createCommand
@@ -75,29 +95,30 @@ async function main() {
75
95
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
76
96
  .option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
77
97
  .action(async ({ publisherMintAddress, keypair, url, dryRun }) => {
78
- try {
98
+ tryWithErrorMessage(async () => {
99
+ await checkForSelfUpdate();
100
+
79
101
  const config = await getConfigFile();
80
102
 
81
103
  if (!hasAddressInConfig(config.publisher) && !publisherMintAddress) {
82
- showUserErrorMessage(
83
- "Either specify an publisher mint address in the config file, or specify as a CLI argument to this command."
84
- );
85
- createCommand.showHelpAfterError();
86
- return;
104
+ throw new Error("Either specify a publisher mint address in the config file or specify as a CLI argument to this command.")
87
105
  }
88
106
 
89
107
  const signer = parseKeypair(keypair);
90
108
  if (signer) {
91
- await createAppCommand({
109
+ const result = await createAppCommand({
92
110
  publisherMintAddress: publisherMintAddress,
93
111
  signer,
94
112
  url,
95
113
  dryRun,
96
114
  });
115
+
116
+ const displayUrl = `https://solscan.io/token/${result.appAddress}${generateNetworkSuffix(url)}`;
117
+ const resultText = `App NFT successfully minted:\n${displayUrl}`;
118
+
119
+ showMessage("Success", resultText);
97
120
  }
98
- } catch (e) {
99
- showUserErrorMessage((e as Error | null)?.message ?? "");
100
- }
121
+ });
101
122
  });
102
123
 
103
124
  createCommand
@@ -118,28 +139,20 @@ async function main() {
118
139
  "Path to Android build tools which contains AAPT2"
119
140
  )
120
141
  .action(async ({ appMintAddress, keypair, url, dryRun, buildToolsPath }) => {
121
- try {
142
+ tryWithErrorMessage(async () => {
143
+ await checkForSelfUpdate();
144
+
122
145
  const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
123
146
  if (resolvedBuildToolsPath === undefined) {
124
- showUserErrorMessage(
125
- "Please specify an Android build tools directory in the .env file or via the command line argument."
126
- );
127
- createCommand.showHelpAfterError();
128
- return;
147
+ throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
129
148
  }
130
149
 
131
150
  const config = await getConfigFile();
132
-
133
151
  if (!hasAddressInConfig(config.app) && !appMintAddress) {
134
- showUserErrorMessage(
135
- "\n\n::: Either specify an app mint address in the config file, or specify as a CLI argument to this command. :::\n\n"
136
- );
137
- createCommand.showHelpAfterError();
138
- return;
152
+ throw new Error("Either specify an app mint address in the config file or specify as a CLI argument to this command")
139
153
  }
140
154
 
141
155
  const signer = parseKeypair(keypair);
142
-
143
156
  if (signer) {
144
157
  const result = await createReleaseCommand({
145
158
  appMintAddress: appMintAddress,
@@ -148,10 +161,13 @@ async function main() {
148
161
  url,
149
162
  dryRun,
150
163
  });
164
+
165
+ const displayUrl = `https://solscan.io/token/${result?.releaseAddress}${generateNetworkSuffix(url)}`;
166
+ const resultText = `Release NFT successfully minted:\n${displayUrl}`;
167
+
168
+ showMessage("Success", resultText);
151
169
  }
152
- } catch (e) {
153
- showUserErrorMessage((e as Error | null)?.message ?? "");
154
- }
170
+ });
155
171
  }
156
172
  );
157
173
 
@@ -167,23 +183,24 @@ async function main() {
167
183
  "Path to Android build tools which contains AAPT2"
168
184
  )
169
185
  .action(async ({ keypair, buildToolsPath }) => {
170
- const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
171
- if (resolvedBuildToolsPath === undefined) {
172
- showUserErrorMessage(
173
- "Please specify an Android build tools directory in the .env file or via the command line argument."
174
- );
175
- createCommand.showHelpAfterError();
176
- return;
177
- }
186
+ tryWithErrorMessage(async () => {
187
+ await checkForSelfUpdate();
178
188
 
179
- const signer = parseKeypair(keypair);
189
+ const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
190
+ if (resolvedBuildToolsPath === undefined) {
191
+ throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
192
+ }
180
193
 
181
- if (signer) {
182
- await validateCommand({
183
- signer,
184
- buildToolsPath: resolvedBuildToolsPath,
185
- });
186
- }
194
+ const signer = parseKeypair(keypair);
195
+ if (signer) {
196
+ await validateCommand({
197
+ signer,
198
+ buildToolsPath: resolvedBuildToolsPath,
199
+ });
200
+
201
+ //TODO: Add pretty formatting here, but will require more work than other sections
202
+ }
203
+ });
187
204
  });
188
205
 
189
206
  const publishCommand = program
@@ -207,9 +224,13 @@ async function main() {
207
224
  "--requestor-is-authorized",
208
225
  "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
209
226
  )
227
+ .option(
228
+ "-a, --app-mint-address <app-mint-address>",
229
+ "The mint address of the app NFT. If not specified, the value from config.yaml will be used."
230
+ )
210
231
  .option(
211
232
  "-r, --release-mint-address <release-mint-address>",
212
- "The mint address of the release NFT"
233
+ "The mint address of the release NFT. If not specified, the value from config.yaml will be used."
213
234
  )
214
235
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
215
236
  .option(
@@ -218,6 +239,7 @@ async function main() {
218
239
  )
219
240
  .action(
220
241
  async ({
242
+ appMintAddress,
221
243
  releaseMintAddress,
222
244
  keypair,
223
245
  url,
@@ -225,20 +247,19 @@ async function main() {
225
247
  requestorIsAuthorized,
226
248
  dryRun,
227
249
  }) => {
228
- try {
250
+ tryWithErrorMessage(async () => {
251
+ await checkForSelfUpdate();
252
+
229
253
  const config = await getConfigFile();
230
254
 
231
255
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
232
- showUserErrorMessage(
233
- "\n\n::: Either specify an release mint address in the config file, or specify as a CLI argument to this command. :::\n\n"
234
- );
235
- publishCommand.showHelpAfterError();
236
- return;
256
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
237
257
  }
238
258
 
239
259
  const signer = parseKeypair(keypair);
240
260
  if (signer) {
241
261
  await publishSubmitCommand({
262
+ appMintAddress,
242
263
  releaseMintAddress,
243
264
  signer,
244
265
  url,
@@ -246,10 +267,11 @@ async function main() {
246
267
  compliesWithSolanaDappStorePolicies,
247
268
  requestorIsAuthorized,
248
269
  });
270
+
271
+ const resultText = "Successfully submitted to the Solana Mobile dApp publisher portal";
272
+ showMessage("Success", resultText);
249
273
  }
250
- } catch (e) {
251
- showUserErrorMessage((e as Error | null)?.message ?? "");
252
- }
274
+ });
253
275
  }
254
276
  );
255
277
 
@@ -270,9 +292,13 @@ async function main() {
270
292
  "--requestor-is-authorized",
271
293
  "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
272
294
  )
295
+ .option(
296
+ "-a, --app-mint-address <app-mint-address>",
297
+ "The mint address of the app NFT. If not specified, the value from config.yaml will be used."
298
+ )
273
299
  .option(
274
300
  "-r, --release-mint-address <release-mint-address>",
275
- "The mint address of the release NFT"
301
+ "The mint address of the release NFT. If not specified, the value from config.yaml will be used."
276
302
  )
277
303
  .option("-c, --critical", "Flag for a critical app update request")
278
304
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
@@ -282,6 +308,7 @@ async function main() {
282
308
  )
283
309
  .action(
284
310
  async ({
311
+ appMintAddress,
285
312
  releaseMintAddress,
286
313
  keypair,
287
314
  url,
@@ -290,21 +317,19 @@ async function main() {
290
317
  critical,
291
318
  dryRun,
292
319
  }) => {
293
- try {
320
+ tryWithErrorMessage(async () => {
321
+ await checkForSelfUpdate();
322
+
294
323
  const config = await getConfigFile();
295
324
 
296
325
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
297
- showUserErrorMessage(
298
- "\n\n::: Either specify an release mint address in the config file, or specify as a CLI argument to this command. :::\n\n"
299
- );
300
- publishCommand.showHelpAfterError();
301
- return;
326
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
302
327
  }
303
328
 
304
329
  const signer = parseKeypair(keypair);
305
-
306
330
  if (signer) {
307
331
  await publishUpdateCommand({
332
+ appMintAddress,
308
333
  releaseMintAddress,
309
334
  signer,
310
335
  url,
@@ -313,10 +338,11 @@ async function main() {
313
338
  requestorIsAuthorized,
314
339
  critical,
315
340
  });
341
+
342
+ const resultText = "dApp successfully updated on the publisher portal";
343
+ showMessage("Success", resultText);
316
344
  }
317
- } catch (e) {
318
- showUserErrorMessage((e as Error | null)?.message ?? "");
319
- }
345
+ });
320
346
  }
321
347
  );
322
348
 
@@ -333,9 +359,13 @@ async function main() {
333
359
  "--requestor-is-authorized",
334
360
  "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
335
361
  )
362
+ .option(
363
+ "-a, --app-mint-address <app-mint-address>",
364
+ "The mint address of the app NFT. If not specified, the value from config.yaml will be used."
365
+ )
336
366
  .option(
337
367
  "-r, --release-mint-address <release-mint-address>",
338
- "The mint address of the release NFT"
368
+ "The mint address of the release NFT. If not specified, the value from config.yaml will be used."
339
369
  )
340
370
  .option("-c, --critical", "Flag for a critical app removal request")
341
371
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
@@ -345,6 +375,7 @@ async function main() {
345
375
  )
346
376
  .action(
347
377
  async ({
378
+ appMintAddress,
348
379
  releaseMintAddress,
349
380
  keypair,
350
381
  url,
@@ -352,21 +383,19 @@ async function main() {
352
383
  critical,
353
384
  dryRun,
354
385
  }) => {
355
- try {
386
+ tryWithErrorMessage(async () => {
387
+ await checkForSelfUpdate();
388
+
356
389
  const config = await getConfigFile();
357
390
 
358
391
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
359
- showUserErrorMessage(
360
- "\n\n::: Either specify an release mint address in the config file, or specify as a CLI argument to this command. :::\n\n"
361
- );
362
- publishCommand.showHelpAfterError();
363
- return;
392
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
364
393
  }
365
394
 
366
395
  const signer = parseKeypair(keypair);
367
-
368
396
  if (signer) {
369
397
  await publishRemoveCommand({
398
+ appMintAddress,
370
399
  releaseMintAddress,
371
400
  signer,
372
401
  url,
@@ -374,10 +403,11 @@ async function main() {
374
403
  requestorIsAuthorized,
375
404
  critical,
376
405
  });
406
+
407
+ const resultText = "dApp successfully removed from the publisher portal";
408
+ showMessage("Success", resultText);
377
409
  }
378
- } catch (e) {
379
- showUserErrorMessage((e as Error | null)?.message ?? "");
380
- }
410
+ })
381
411
  }
382
412
  );
383
413
 
@@ -394,9 +424,13 @@ async function main() {
394
424
  "--requestor-is-authorized",
395
425
  "An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
396
426
  )
427
+ .option(
428
+ "-a, --app-mint-address <app-mint-address>",
429
+ "The mint address of the app NFT. If not specified, the value from config.yaml will be used."
430
+ )
397
431
  .option(
398
432
  "-r, --release-mint-address <release-mint-address>",
399
- "The mint address of the release NFT"
433
+ "The mint address of the release NFT. If not specified, the value from config.yaml will be used."
400
434
  )
401
435
  .option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
402
436
  .option(
@@ -406,23 +440,21 @@ async function main() {
406
440
  .action(
407
441
  async (
408
442
  requestDetails,
409
- { releaseMintAddress, keypair, url, requestorIsAuthorized, dryRun }
443
+ { appMintAddress, releaseMintAddress, keypair, url, requestorIsAuthorized, dryRun }
410
444
  ) => {
411
- try {
445
+ tryWithErrorMessage(async () => {
446
+ await checkForSelfUpdate();
447
+
412
448
  const config = await getConfigFile();
413
449
 
414
450
  if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
415
- showUserErrorMessage(
416
- "\n\n::: Either specify an release mint address in the config file, or specify as a CLI argument to this command. :::\n\n"
417
- );
418
- publishCommand.showHelpAfterError();
419
- return;
451
+ throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
420
452
  }
421
453
 
422
454
  const signer = parseKeypair(keypair);
423
-
424
455
  if (signer) {
425
456
  await publishSupportCommand({
457
+ appMintAddress,
426
458
  releaseMintAddress,
427
459
  signer,
428
460
  url,
@@ -430,10 +462,11 @@ async function main() {
430
462
  requestorIsAuthorized,
431
463
  requestDetails,
432
464
  });
465
+
466
+ const resultText = "Support request sent successfully";
467
+ showMessage("Success", resultText);
433
468
  }
434
- } catch (e) {
435
- showUserErrorMessage((e as Error | null)?.message ?? "");
436
- }
469
+ });
437
470
  }
438
471
  );
439
472
 
package/src/utils.ts CHANGED
@@ -11,6 +11,9 @@ import { exec } from "child_process";
11
11
  import * as path from "path";
12
12
  import { BundlrStorageDriver, keypairIdentity, Metaplex, toMetaplexFile } from "@metaplex-foundation/js";
13
13
  import { imageSize } from "image-size";
14
+ import updateNotifier from "update-notifier";
15
+ import cliPackage from "./package.json" assert { type: "json" };
16
+ import boxen from "boxen";
14
17
 
15
18
  import { CachedStorageDriver } from "./upload/CachedStorageDriver.js";
16
19
 
@@ -19,6 +22,15 @@ const runExec = util.promisify(exec);
19
22
 
20
23
  export const debug = debugModule("CLI");
21
24
 
25
+ export const checkForSelfUpdate = async () => {
26
+ const notifier = updateNotifier({ pkg: cliPackage });
27
+ const updateInfo = await notifier.fetchInfo();
28
+
29
+ if (updateInfo.current != updateInfo.latest) {
30
+ throw new Error("Please update to the latest version of the dApp Store CLI before proceeding.");
31
+ }
32
+ };
33
+
22
34
  export const parseKeypair = (pathToKeypairFile: string) => {
23
35
  try {
24
36
  const keypairFile = fs.readFileSync(pathToKeypairFile, "utf-8");
@@ -48,8 +60,6 @@ export const getConfigFile = async (
48
60
 
49
61
  const config = await getConfig(configFilePath);
50
62
 
51
- console.info(`Pulling details from ${configFilePath}`);
52
-
53
63
  if (buildToolsDir && fs.lstatSync(buildToolsDir).isDirectory()) {
54
64
  // We validate that the config is going to have at least one installable asset
55
65
  const apkEntry = config.release.files.find(
@@ -122,10 +132,34 @@ const checkImageExtension = (uri: string): boolean => {
122
132
  );
123
133
  };
124
134
 
125
- export const showUserErrorMessage = (msg: string) => {
126
- console.error("\n:::: Solana Publish CLI: Error Message ::::");
127
- console.error(msg);
128
- console.error("");
135
+ export const generateNetworkSuffix = (rpcUrl: string): string => {
136
+ let suffix = "";
137
+
138
+ if (rpcUrl.indexOf("devnet") != -1) {
139
+ suffix = "?cluster=devnet";
140
+ } else if (rpcUrl.indexOf("testnet") != -1) {
141
+ suffix = "?cluster=testnet";
142
+ } else if (rpcUrl.indexOf("mainnet") != -1) {
143
+ suffix = "?cluster=mainnet";
144
+ }
145
+
146
+ return suffix;
147
+ };
148
+
149
+ export const showMessage = (
150
+ titleMessage = "",
151
+ contentMessage = "",
152
+ isError = false
153
+ ) => {
154
+ console.log(boxen(contentMessage, {
155
+ title: titleMessage,
156
+ padding: 1,
157
+ margin: 1,
158
+ borderStyle: 'single',
159
+ borderColor: isError ? "redBright" : "cyan",
160
+ textAlignment: "left",
161
+ titleAlignment: "center"
162
+ }));
129
163
  };
130
164
 
131
165
  const checkIconDimensions = async (iconPath: string): Promise<boolean> => {
@@ -167,7 +201,7 @@ const getAndroidDetails = async (
167
201
  let localeArray = Array.from(locales?.values() ?? []);
168
202
  if (localeArray.length == 2) {
169
203
  const localesSrc = localeArray[1];
170
- localeArray = localesSrc.split("' '").slice(1);
204
+ localeArray = ["en-US"].concat(localesSrc.split("' '").slice(1));
171
205
  }
172
206
 
173
207
  return {