@solana-mobile/dapp-store-cli 0.8.1 → 0.9.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.
@@ -219,6 +219,7 @@ import { Constants, showMessage } from "../CliUtils.js";
219
219
  import util from "util";
220
220
  import { imageSize } from "image-size";
221
221
  import { exec } from "child_process";
222
+ import getVideoDimensions from "get-video-dimensions";
222
223
  var runImgSize = util.promisify(imageSize);
223
224
  var runExec = util.promisify(exec);
224
225
  var AaptPrefixes = {
@@ -265,7 +266,7 @@ export var loadPublishDetails = function() {
265
266
  }();
266
267
  export var loadPublishDetailsWithChecks = function() {
267
268
  var _ref = _async_to_generator(function() {
268
- var buildToolsDir, _config_publisher_media_find, _config_publisher_media, _config_app_media_find, _config_app_media, _config_release_media_find, _config_release_media, _config_release_media1, config, apkEntry, apkPath, _, publisherIcon, iconPath, iconBuffer, appIcon, iconPath1, iconBuffer1, releaseIcon, iconPath2, screenshots, googlePkg, pkgCompare;
269
+ var buildToolsDir, _config_publisher_media_find, _config_publisher_media, _config_app_media_find, _config_app_media, _config_release_media_find, _config_release_media, _config_release_media1, _config_release_media2, config, apkEntry, apkPath, _, publisherIcon, iconPath, iconBuffer, appIcon, iconPath1, iconBuffer1, releaseIcon, iconPath2, screenshots, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, item, mediaPath, err, videos, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, video, mediaPath1, err, googlePkg, pkgCompare;
269
270
  var _arguments = arguments;
270
271
  return _ts_generator(this, function(_state) {
271
272
  switch(_state.label){
@@ -363,16 +364,145 @@ export var loadPublishDetailsWithChecks = function() {
363
364
  throw new Error("Please specify at least one media entry of type icon in your configuration file");
364
365
  }
365
366
  config.release.media.forEach(function(item) {
366
- var imagePath = path.join(process.cwd(), item.uri);
367
- if (!fs.existsSync(imagePath) || !checkImageExtension(imagePath)) {
368
- throw new Error("Invalid media path or file type: ".concat(item.uri, ". Please ensure the file is a jpeg, png, or webp file."));
367
+ var mediaPath = path.join(process.cwd(), item.uri);
368
+ if (!fs.existsSync(mediaPath)) {
369
+ throw new Error("File doesnt exist: ".concat(item.uri, "."));
370
+ }
371
+ if (item.purpose == "screenshot" && !checkImageExtension(mediaPath)) {
372
+ throw new Error("Please ensure the file ".concat(item.uri, " is a jpeg, png, or webp file."));
373
+ }
374
+ if (item.purpose == "video" && !checkVideoExtension(mediaPath)) {
375
+ throw new Error("Please ensure the file ".concat(item.uri, " is a mp4."));
369
376
  }
370
377
  });
371
378
  screenshots = (_config_release_media1 = config.release.media) === null || _config_release_media1 === void 0 ? void 0 : _config_release_media1.filter(function(asset) {
372
379
  return asset.purpose === "screenshot";
373
380
  });
374
- if (screenshots.length < 4) {
375
- showMessage("Screenshots requirements changing in version 0.9.0", "At least 4 screenshots are required for publishing a new release. Found only ".concat(screenshots.length), "warning");
381
+ _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
382
+ _state.label = 12;
383
+ case 12:
384
+ _state.trys.push([
385
+ 12,
386
+ 17,
387
+ 18,
388
+ 19
389
+ ]);
390
+ _iterator = screenshots[Symbol.iterator]();
391
+ _state.label = 13;
392
+ case 13:
393
+ if (!!(_iteratorNormalCompletion = (_step = _iterator.next()).done)) return [
394
+ 3,
395
+ 16
396
+ ];
397
+ item = _step.value;
398
+ mediaPath = path.join(process.cwd(), item.uri);
399
+ return [
400
+ 4,
401
+ checkScreenshotSize(mediaPath)
402
+ ];
403
+ case 14:
404
+ if (_state.sent()) {
405
+ throw new Error("Screenshot ".concat(mediaPath, " must be at least 1080px in width and height."));
406
+ }
407
+ _state.label = 15;
408
+ case 15:
409
+ _iteratorNormalCompletion = true;
410
+ return [
411
+ 3,
412
+ 13
413
+ ];
414
+ case 16:
415
+ return [
416
+ 3,
417
+ 19
418
+ ];
419
+ case 17:
420
+ err = _state.sent();
421
+ _didIteratorError = true;
422
+ _iteratorError = err;
423
+ return [
424
+ 3,
425
+ 19
426
+ ];
427
+ case 18:
428
+ try {
429
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
430
+ _iterator.return();
431
+ }
432
+ } finally{
433
+ if (_didIteratorError) {
434
+ throw _iteratorError;
435
+ }
436
+ }
437
+ return [
438
+ 7
439
+ ];
440
+ case 19:
441
+ videos = (_config_release_media2 = config.release.media) === null || _config_release_media2 === void 0 ? void 0 : _config_release_media2.filter(function(asset) {
442
+ return asset.purpose === "video";
443
+ });
444
+ _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
445
+ _state.label = 20;
446
+ case 20:
447
+ _state.trys.push([
448
+ 20,
449
+ 25,
450
+ 26,
451
+ 27
452
+ ]);
453
+ _iterator1 = videos[Symbol.iterator]();
454
+ _state.label = 21;
455
+ case 21:
456
+ if (!!(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done)) return [
457
+ 3,
458
+ 24
459
+ ];
460
+ video = _step1.value;
461
+ mediaPath1 = path.join(process.cwd(), video.uri);
462
+ return [
463
+ 4,
464
+ checkVideoSize(mediaPath1)
465
+ ];
466
+ case 22:
467
+ if (_state.sent()) {
468
+ throw new Error("Video ".concat(mediaPath1, " must be at least 720px in width and height."));
469
+ }
470
+ _state.label = 23;
471
+ case 23:
472
+ _iteratorNormalCompletion1 = true;
473
+ return [
474
+ 3,
475
+ 21
476
+ ];
477
+ case 24:
478
+ return [
479
+ 3,
480
+ 27
481
+ ];
482
+ case 25:
483
+ err = _state.sent();
484
+ _didIteratorError1 = true;
485
+ _iteratorError1 = err;
486
+ return [
487
+ 3,
488
+ 27
489
+ ];
490
+ case 26:
491
+ try {
492
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
493
+ _iterator1.return();
494
+ }
495
+ } finally{
496
+ if (_didIteratorError1) {
497
+ throw _iteratorError1;
498
+ }
499
+ }
500
+ return [
501
+ 7
502
+ ];
503
+ case 27:
504
+ if (screenshots.length + videos.length < 4) {
505
+ throw new Error("At least 4 screenshots or videos are required for publishing a new release. Found only ".concat(screenshots.length + videos.length));
376
506
  }
377
507
  validateLocalizableResources(config);
378
508
  googlePkg = config.solana_mobile_dapp_publisher_portal.google_store_package;
@@ -423,6 +553,10 @@ var checkImageExtension = function(uri) {
423
553
  var fileExt = path.extname(uri).toLowerCase();
424
554
  return fileExt == ".png" || fileExt == ".jpg" || fileExt == ".jpeg" || fileExt == ".webp";
425
555
  };
556
+ var checkVideoExtension = function(uri) {
557
+ var fileExt = path.extname(uri).toLowerCase();
558
+ return fileExt == ".mp4";
559
+ };
426
560
  /**
427
561
  * We need to pre-check some things in the localized resources before we move forward
428
562
  */ var validateLocalizableResources = function(config) {
@@ -468,6 +602,52 @@ var checkIconDimensions = function() {
468
602
  return _ref.apply(this, arguments);
469
603
  };
470
604
  }();
605
+ var checkScreenshotSize = function() {
606
+ var _ref = _async_to_generator(function(imagePath) {
607
+ var size, _size_width, _size_height;
608
+ return _ts_generator(this, function(_state) {
609
+ switch(_state.label){
610
+ case 0:
611
+ return [
612
+ 4,
613
+ runImgSize(imagePath)
614
+ ];
615
+ case 1:
616
+ size = _state.sent();
617
+ return [
618
+ 2,
619
+ ((_size_width = size === null || size === void 0 ? void 0 : size.width) !== null && _size_width !== void 0 ? _size_width : 0) < 1080 || ((_size_height = size === null || size === void 0 ? void 0 : size.height) !== null && _size_height !== void 0 ? _size_height : 0) < 1080
620
+ ];
621
+ }
622
+ });
623
+ });
624
+ return function checkScreenshotSize(imagePath) {
625
+ return _ref.apply(this, arguments);
626
+ };
627
+ }();
628
+ var checkVideoSize = function() {
629
+ var _ref = _async_to_generator(function(imagePath) {
630
+ var size, _size_width, _size_height;
631
+ return _ts_generator(this, function(_state) {
632
+ switch(_state.label){
633
+ case 0:
634
+ return [
635
+ 4,
636
+ getVideoDimensions(imagePath)
637
+ ];
638
+ case 1:
639
+ size = _state.sent();
640
+ return [
641
+ 2,
642
+ ((_size_width = size === null || size === void 0 ? void 0 : size.width) !== null && _size_width !== void 0 ? _size_width : 0) < 720 || ((_size_height = size === null || size === void 0 ? void 0 : size.height) !== null && _size_height !== void 0 ? _size_height : 0) < 720
643
+ ];
644
+ }
645
+ });
646
+ });
647
+ return function checkVideoSize(imagePath) {
648
+ return _ref.apply(this, arguments);
649
+ };
650
+ }();
471
651
  var getAndroidDetails = function() {
472
652
  var _ref = _async_to_generator(function(aaptDir, apkPath) {
473
653
  var stdout, appPackage, versionCode, versionName, minSdk, permissions, locales, isDebuggable, _locales_values, localeArray, localesSrc, _appPackage_, _minSdk_, _versionCode_, _versionName_, _tmp, e;
@@ -490,7 +670,9 @@ var getAndroidDetails = function() {
490
670
  versionCode = new RegExp(AaptPrefixes.verCodePrefix + AaptPrefixes.quoteRegex).exec(stdout);
491
671
  versionName = new RegExp(AaptPrefixes.verNamePrefix + AaptPrefixes.quoteRegex).exec(stdout);
492
672
  minSdk = new RegExp(AaptPrefixes.sdkPrefix + AaptPrefixes.quoteRegex).exec(stdout);
493
- permissions = _to_consumable_array(stdout.matchAll(/uses-permission: name='(.*)'/g));
673
+ permissions = _to_consumable_array(stdout.matchAll(/uses-permission: name='(.*)'/g)).flatMap(function(permission) {
674
+ return permission[1];
675
+ });
494
676
  locales = new RegExp(AaptPrefixes.localePrefix + AaptPrefixes.quoteNonLazyRegex).exec(stdout);
495
677
  isDebuggable = new RegExp(AaptPrefixes.debuggableApkPrefix).exec(stdout);
496
678
  if (isDebuggable != null) {
@@ -503,9 +685,19 @@ var getAndroidDetails = function() {
503
685
  "en-US"
504
686
  ].concat(localesSrc.split("' '").slice(1));
505
687
  }
688
+ if (permissions.includes("android.permission.INSTALL_PACKAGES") || permissions.includes("android.permission.DELETE_PACKAGES")) {
689
+ showMessage("App requests system app install/delete permission", "Your app requests system install/delete permission which is managed by Solana dApp Store.\nThis app will be not approved for listing on Solana dApp Store.", "error");
690
+ }
691
+ if (permissions.includes("android.permission.REQUEST_INSTALL_PACKAGES") || permissions.includes("android.permission.REQUEST_DELETE_PACKAGES")) {
692
+ showMessage("App requests install or delete permission", "App will be subject to additional security reviews for listing on Solana dApp Store and processing time may be beyond regular review time", "warning");
693
+ }
694
+ if (permissions.includes("com.solanamobile.seedvault.ACCESS_SEED_VAULT")) {
695
+ showMessage("App requests Seed Vault permission", "If this is not a wallet application, your app maybe rejected from listing on Solana dApp Store.", "warning");
696
+ }
506
697
  if (localeArray.length >= 60) {
507
698
  showMessage("The bundle apk claims supports for following locales", "Claim for supported locales::\n" + localeArray + "\nIf this release does not support all these locales the release may be rejected" + "\nSee details at https://developer.android.com/guide/topics/resources/multilingual-support#design for configuring the supported locales", "warning");
508
699
  }
700
+ checkAbis(apkPath);
509
701
  _tmp = {
510
702
  android_package: (_appPackage_ = appPackage === null || appPackage === void 0 ? void 0 : appPackage[1]) !== null && _appPackage_ !== void 0 ? _appPackage_ : "",
511
703
  min_sdk: parseInt((_minSdk_ = minSdk === null || minSdk === void 0 ? void 0 : minSdk[1]) !== null && _minSdk_ !== void 0 ? _minSdk_ : "0", 10),
@@ -519,9 +711,7 @@ var getAndroidDetails = function() {
519
711
  case 2:
520
712
  return [
521
713
  2,
522
- (_tmp.cert_fingerprint = _state.sent(), _tmp.permissions = permissions.flatMap(function(permission) {
523
- return permission[1];
524
- }), _tmp.locales = localeArray, _tmp)
714
+ (_tmp.cert_fingerprint = _state.sent(), _tmp.permissions = permissions, _tmp.locales = localeArray, _tmp)
525
715
  ];
526
716
  case 3:
527
717
  e = _state.sent();
@@ -545,6 +735,70 @@ var getAndroidDetails = function() {
545
735
  return _ref.apply(this, arguments);
546
736
  };
547
737
  }();
738
+ var checkAbis = function() {
739
+ var _ref = _async_to_generator(function(apkPath) {
740
+ var stdout, amV7libs, x86libs, x8664libs, messages, e;
741
+ return _ts_generator(this, function(_state) {
742
+ switch(_state.label){
743
+ case 0:
744
+ _state.trys.push([
745
+ 0,
746
+ 2,
747
+ ,
748
+ 3
749
+ ]);
750
+ return [
751
+ 4,
752
+ runExec("zipinfo -s ".concat(apkPath, " | grep .so$"))
753
+ ];
754
+ case 1:
755
+ stdout = _state.sent().stdout;
756
+ amV7libs = _to_consumable_array(stdout.matchAll(/lib\/armeabi-v7a\/(.*)/g)).flatMap(function(permission) {
757
+ return permission[1];
758
+ });
759
+ x86libs = _to_consumable_array(stdout.matchAll(/lib\/x86\/(.*)/g)).flatMap(function(permission) {
760
+ return permission[1];
761
+ });
762
+ x8664libs = _to_consumable_array(stdout.matchAll(/lib\/x86_64\/(.*)/g)).flatMap(function(permission) {
763
+ return permission[1];
764
+ });
765
+ if (amV7libs.length > 0 || x86libs.length > 0 || x8664libs.length > 0) {
766
+ messages = [
767
+ "Solana dApp Store only supports arm64-v8a abi.",
768
+ "Your apk file contains following unsupported abis"
769
+ ].concat(_to_consumable_array(amV7libs.length > 0 ? [
770
+ "\narmeabi-v7a:\n" + amV7libs
771
+ ] : []), _to_consumable_array(x86libs.length > 0 ? [
772
+ "\nx86:\n" + x86libs
773
+ ] : []), _to_consumable_array(x8664libs.length > 0 ? [
774
+ "\nx86_64:\n" + x8664libs
775
+ ] : []), [
776
+ "\n\nAlthough your app works fine on Saga, these library files are unused and increase the size of apk file making the download and update time longer for your app.",
777
+ "\n\nSee https://developer.android.com/games/optimize/64-bit#build-with-64-bit for how to optimize your app."
778
+ ]).join("\n");
779
+ showMessage("Unsupported files found in apk", messages, "warning");
780
+ }
781
+ return [
782
+ 3,
783
+ 3
784
+ ];
785
+ case 2:
786
+ e = _state.sent();
787
+ return [
788
+ 3,
789
+ 3
790
+ ];
791
+ case 3:
792
+ return [
793
+ 2
794
+ ];
795
+ }
796
+ });
797
+ });
798
+ return function checkAbis(apkPath) {
799
+ return _ref.apply(this, arguments);
800
+ };
801
+ }();
548
802
  export var extractCertFingerprint = function() {
549
803
  var _ref = _async_to_generator(function(aaptDir, apkPath) {
550
804
  var stdout, regex, match;
@@ -579,11 +833,11 @@ export var extractCertFingerprint = function() {
579
833
  }();
580
834
  export var writeToPublishDetails = function() {
581
835
  var _ref = _async_to_generator(function(param) {
582
- var publisher, app, release, currentConfig, _publisher_address, _app_address, _release_address, newConfig;
836
+ var publisher, app, release, lastSubmittedVersionOnChain, lastUpdatedVersionOnStore, currentConfig, _publisher_address, _app_address, _release_address, newConfig;
583
837
  return _ts_generator(this, function(_state) {
584
838
  switch(_state.label){
585
839
  case 0:
586
- publisher = param.publisher, app = param.app, release = param.release;
840
+ publisher = param.publisher, app = param.app, release = param.release, lastSubmittedVersionOnChain = param.lastSubmittedVersionOnChain, lastUpdatedVersionOnStore = param.lastUpdatedVersionOnStore;
587
841
  return [
588
842
  4,
589
843
  loadPublishDetailsWithChecks()
@@ -602,7 +856,9 @@ export var writeToPublishDetails = function() {
602
856
  release: _object_spread_props(_object_spread({}, currentConfig.release), {
603
857
  address: (_release_address = release === null || release === void 0 ? void 0 : release.address) !== null && _release_address !== void 0 ? _release_address : currentConfig.release.address
604
858
  }),
605
- solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal
859
+ solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal,
860
+ lastSubmittedVersionOnChain: lastSubmittedVersionOnChain !== null && lastSubmittedVersionOnChain !== void 0 ? lastSubmittedVersionOnChain : currentConfig.lastSubmittedVersionOnChain,
861
+ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore !== null && lastUpdatedVersionOnStore !== void 0 ? lastUpdatedVersionOnStore : currentConfig.lastUpdatedVersionOnStore
606
862
  };
607
863
  fs.writeFileSync(Constants.getConfigFilePath(), dump(newConfig, {
608
864
  lineWidth: -1
@@ -1 +1 @@
1
- {"publisher":{"name":"<<YOUR_PUBLISHER_NAME>>","address":"","website":"<<URL_OF_PUBLISHER_WEBSITE>>","email":"<<EMAIL_ADDRESS_TO_CONTACT_PUBLISHER>>","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_PUBLISHER_ICON>>"}]},"app":{"name":"<<APP_NAME>>","address":"","android_package":"<<ANDROID_PACKAGE_NAME>>","urls":{"license_url":"<<URL_OF_APP_LICENSE_OR_TERMS_OF_SERVICE>>","copyright_url":"<<URL_OF_COPYRIGHT_DETAILS_FOR_APP>>","privacy_policy_url":"<<URL_OF_APP_PRIVACY_POLICY>>","website":"<<URL_OF_APP_WEBSITE>>"},"media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_APP_ICON>>"}]},"release":{"address":"","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_RELEASE_ICON>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT>>"}],"files":[{"purpose":"install","uri":"<<RELATIVE_PATH_TO_APK>>"}],"catalog":{"en-US":{"name":"<<APP_NAME>>","short_description":"<<SHORT_APP_DESCRIPTION>>","long_description":"<<LONG_APP_DESCRIPTION>>","new_in_version":"<<WHATS_NEW_IN_THIS_VERSION>>","saga_features":"<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>"}}},"solana_mobile_dapp_publisher_portal":{"google_store_package":"<<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>","testing_instructions":"<<TESTING_INSTRUCTIONS>>"}}
1
+ {"publisher":{"name":"<<YOUR_PUBLISHER_NAME>>","address":"","website":"<<URL_OF_PUBLISHER_WEBSITE>>","email":"<<EMAIL_ADDRESS_TO_CONTACT_PUBLISHER>>","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_PUBLISHER_ICON>>"}]},"app":{"name":"<<APP_NAME>>","address":"","android_package":"<<ANDROID_PACKAGE_NAME>>","urls":{"license_url":"<<URL_OF_APP_LICENSE_OR_TERMS_OF_SERVICE>>","copyright_url":"<<URL_OF_COPYRIGHT_DETAILS_FOR_APP>>","privacy_policy_url":"<<URL_OF_APP_PRIVACY_POLICY>>","website":"<<URL_OF_APP_WEBSITE>>"},"media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_APP_ICON>>"}]},"release":{"address":"","media":[{"purpose":"icon","uri":"<<RELATIVE_PATH_TO_RELEASE_ICON>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT1>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT2>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT3>>"},{"purpose":"screenshot","uri":"<<RELATIVE_PATH_TO_SCREENSHOT4>>"},{"purpose":"video","uri":"<<RELATIVE_PATH_TO_VIDEO1>>"}],"files":[{"purpose":"install","uri":"<<RELATIVE_PATH_TO_APK>>"}],"catalog":{"en-US":{"name":"<<APP_NAME>>","short_description":"<<SHORT_APP_DESCRIPTION>>","long_description":"<<LONG_APP_DESCRIPTION>>","new_in_version":"<<WHATS_NEW_IN_THIS_VERSION>>","saga_features":"<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>"}}},"solana_mobile_dapp_publisher_portal":{"google_store_package":"<<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>","testing_instructions":"<<TESTING_INSTRUCTIONS>>"}}
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-mobile/dapp-store-cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -34,9 +34,9 @@
34
34
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
35
35
  },
36
36
  "devDependencies": {
37
- "@metaplex-foundation/js": "0.18.3",
38
37
  "@jest/globals": "^29.5.0",
39
38
  "@jest/types": "^29.5.0",
39
+ "@metaplex-foundation/js": "0.20.0",
40
40
  "@swc/jest": "^0.2.26",
41
41
  "@types/commander": "^2.12.2",
42
42
  "@types/debug": "^4.1.7",
@@ -52,8 +52,8 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@aws-sdk/client-s3": "^3.321.1",
55
- "@metaplex-foundation/js-plugin-aws": "^0.18.3",
56
- "@solana-mobile/dapp-store-publishing-tools": "workspace:0.8.1",
55
+ "@metaplex-foundation/js-plugin-aws": "^0.20.0",
56
+ "@solana-mobile/dapp-store-publishing-tools": "workspace:0.9.0",
57
57
  "@solana/web3.js": "1.68.0",
58
58
  "@types/semver": "^7.3.13",
59
59
  "ajv": "^8.11.0",
@@ -64,6 +64,7 @@
64
64
  "dotenv": "^16.0.3",
65
65
  "esm": "^3.2.25",
66
66
  "generate-schema": "^2.6.0",
67
+ "get-video-dimensions": "^1.0.0",
67
68
  "image-size": "^1.0.2",
68
69
  "js-yaml": "^4.1.0",
69
70
  "semver": "^7.3.8",
@@ -24,7 +24,15 @@ release:
24
24
  - purpose: icon
25
25
  uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
26
26
  - purpose: screenshot
27
- uri: <<RELATIVE_PATH_TO_SCREENSHOT>>
27
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT1>>
28
+ - purpose: screenshot
29
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT2>>
30
+ - purpose: screenshot
31
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT3>>
32
+ - purpose: screenshot
33
+ uri: <<RELATIVE_PATH_TO_SCREENSHOT4>>
34
+ - purpose: video
35
+ uri: <<RELATIVE_PATH_TO_VIDEO1>>
28
36
  files:
29
37
  - purpose: install
30
38
  uri: <<RELATIVE_PATH_TO_APK>>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solana-mobile/dapp-store-cli",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -27,9 +27,9 @@
27
27
  "access": "public"
28
28
  },
29
29
  "devDependencies": {
30
- "@metaplex-foundation/js": "0.18.3",
31
30
  "@jest/globals": "^29.5.0",
32
31
  "@jest/types": "^29.5.0",
32
+ "@metaplex-foundation/js": "0.20.0",
33
33
  "@swc/jest": "^0.2.26",
34
34
  "@types/commander": "^2.12.2",
35
35
  "@types/debug": "^4.1.7",
@@ -45,8 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@aws-sdk/client-s3": "^3.321.1",
48
- "@metaplex-foundation/js-plugin-aws": "^0.18.3",
49
- "@solana-mobile/dapp-store-publishing-tools": "0.8.1",
48
+ "@metaplex-foundation/js-plugin-aws": "^0.20.0",
49
+ "@solana-mobile/dapp-store-publishing-tools": "0.9.0",
50
50
  "@solana/web3.js": "1.68.0",
51
51
  "@types/semver": "^7.3.13",
52
52
  "ajv": "^8.11.0",
@@ -57,6 +57,7 @@
57
57
  "dotenv": "^16.0.3",
58
58
  "esm": "^3.2.25",
59
59
  "generate-schema": "^2.6.0",
60
+ "get-video-dimensions": "^1.0.0",
60
61
  "image-size": "^1.0.2",
61
62
  "js-yaml": "^4.1.0",
62
63
  "semver": "^7.3.8",
package/src/CliSetup.ts CHANGED
@@ -14,7 +14,8 @@ import {
14
14
  dryRunSuccessMessage,
15
15
  generateNetworkSuffix,
16
16
  parseKeypair,
17
- showMessage
17
+ showMessage,
18
+ showNetworkWarningIfApplicable
18
19
  } from "./CliUtils.js";
19
20
  import { LAMPORTS_PER_SOL } from "@solana/web3.js"
20
21
  import * as dotenv from "dotenv";
@@ -49,6 +50,7 @@ function resolveBuildToolsPath(buildToolsPath: string | undefined) {
49
50
  */
50
51
  function latestReleaseMessage() {
51
52
  const messages = [
53
+ `- App details page now supports video files. (mp4 file format only and minimum resolution 720p)`,
52
54
  `- priority fee has been updated to ${Constants.DEFAULT_PRIORITY_FEE} lamports = ${Constants.DEFAULT_PRIORITY_FEE / LAMPORTS_PER_SOL} SOL. To adjust this value use param "-p" or "--priority-fee-lamports"`,
53
55
  `- At least 4 screenshots are now required to update or release a new app`,
54
56
  `- App icons should be exactly 512x512.`
@@ -104,6 +106,7 @@ export const createPublisherCliCmd = createCliCmd
104
106
  .option("-p, --priority-fee-lamports <priority-fee-lamports>", "Priority Fee lamports")
105
107
  .action(async ({ keypair, url, dryRun, storageConfig, priorityFeeLamports }) => {
106
108
  await tryWithErrorMessage(async () => {
109
+ showNetworkWarningIfApplicable(url)
107
110
  latestReleaseMessage();
108
111
  await checkForSelfUpdate();
109
112
 
@@ -141,6 +144,7 @@ export const createAppCliCmd = createCliCmd
141
144
  .option("-p, --priority-fee-lamports <priority-fee-lamports>", "Priority Fee lamports")
142
145
  .action(async ({ publisherMintAddress, keypair, url, dryRun, storageConfig, priorityFeeLamports }) => {
143
146
  await tryWithErrorMessage(async () => {
147
+ showNetworkWarningIfApplicable(url)
144
148
  latestReleaseMessage();
145
149
  await checkForSelfUpdate();
146
150
 
@@ -194,6 +198,7 @@ export const createReleaseCliCmd = createCliCmd
194
198
  .option("-p, --priority-fee-lamports <priority-fee-lamports>", "Priority Fee lamports")
195
199
  .action(async ({ appMintAddress, keypair, url, dryRun, buildToolsPath, storageConfig, priorityFeeLamports }) => {
196
200
  await tryWithErrorMessage(async () => {
201
+ showNetworkWarningIfApplicable(url)
197
202
  latestReleaseMessage();
198
203
  await checkForSelfUpdate();
199
204
 
@@ -310,7 +315,7 @@ publishCommand
310
315
  }) => {
311
316
  await tryWithErrorMessage(async () => {
312
317
  await checkForSelfUpdate();
313
- await checkSubmissionNetwork(url);
318
+ checkSubmissionNetwork(url);
314
319
 
315
320
  const config = await loadPublishDetails(Constants.getConfigFilePath());
316
321
 
@@ -320,15 +325,28 @@ publishCommand
320
325
 
321
326
  const signer = parseKeypair(keypair);
322
327
  if (signer) {
323
- await publishSubmitCommand({
324
- appMintAddress,
325
- releaseMintAddress,
326
- signer,
327
- url,
328
- dryRun,
329
- compliesWithSolanaDappStorePolicies,
330
- requestorIsAuthorized,
331
- });
328
+ if (config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null) {
329
+ await publishUpdateCommand({
330
+ appMintAddress: appMintAddress,
331
+ releaseMintAddress: releaseMintAddress,
332
+ signer: signer,
333
+ url: url,
334
+ dryRun: dryRun,
335
+ compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
336
+ requestorIsAuthorized: requestorIsAuthorized,
337
+ critical: false,
338
+ });
339
+ } else {
340
+ await publishSubmitCommand({
341
+ appMintAddress: appMintAddress,
342
+ releaseMintAddress: releaseMintAddress,
343
+ signer: signer,
344
+ url: url,
345
+ dryRun: dryRun,
346
+ compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
347
+ requestorIsAuthorized: requestorIsAuthorized,
348
+ });
349
+ }
332
350
 
333
351
  if (dryRun) {
334
352
  dryRunSuccessMessage()
@@ -384,7 +402,7 @@ publishCommand
384
402
  }) => {
385
403
  await tryWithErrorMessage(async () => {
386
404
  await checkForSelfUpdate();
387
- await checkSubmissionNetwork(url);
405
+ checkSubmissionNetwork(url);
388
406
 
389
407
  const config = await loadPublishDetails(Constants.getConfigFilePath())
390
408
 
@@ -454,7 +472,7 @@ publishCommand
454
472
  }) => {
455
473
  await tryWithErrorMessage(async () => {
456
474
  await checkForSelfUpdate();
457
- await checkSubmissionNetwork(url);
475
+ checkSubmissionNetwork(url);
458
476
 
459
477
  const config = await loadPublishDetails(Constants.getConfigFilePath())
460
478
 
@@ -517,7 +535,7 @@ publishCommand
517
535
  ) => {
518
536
  await tryWithErrorMessage(async () => {
519
537
  await checkForSelfUpdate();
520
- await checkSubmissionNetwork(url);
538
+ checkSubmissionNetwork(url);
521
539
 
522
540
  const config = await loadPublishDetails(Constants.getConfigFilePath())
523
541
 
package/src/CliUtils.ts CHANGED
@@ -3,7 +3,7 @@ import type { Connection } from "@solana/web3.js";
3
3
  import { Keypair, PublicKey } from "@solana/web3.js";
4
4
  import debugModule from "debug";
5
5
  import {
6
- BundlrStorageDriver,
6
+ IrysStorageDriver,
7
7
  keypairIdentity,
8
8
  Metaplex,
9
9
  } from "@metaplex-foundation/js";
@@ -18,7 +18,7 @@ import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
18
18
  import { S3StorageManager } from "./config/index.js";
19
19
 
20
20
  export class Constants {
21
- static CLI_VERSION = "0.8.1";
21
+ static CLI_VERSION = "0.9.0";
22
22
  static CONFIG_FILE_NAME = "config.yaml";
23
23
  static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
24
24
  static DEFAULT_PRIORITY_FEE = 500000;
@@ -42,7 +42,7 @@ export const checkForSelfUpdate = async () => {
42
42
  latestVer.minor > currentVer.minor
43
43
  ) {
44
44
  throw new Error(
45
- "Please update to the latest version of the dApp Store CLI before proceeding."
45
+ `Please update to the latest version of the dApp Store CLI before proceeding.\nCurrent version is ${currentVer.raw}\nLatest version is ${latestVer.raw}`
46
46
  );
47
47
  }
48
48
  };
@@ -117,6 +117,14 @@ export const dryRunSuccessMessage = () => {
117
117
  showMessage("Dry run", "Dry run was successful", "standard")
118
118
  }
119
119
 
120
+ export const showNetworkWarningIfApplicable = (rpcUrl: string) => {
121
+ if (isDevnet(rpcUrl)) {
122
+ showMessage("Devnet Mode", "Running on Devnet", "warning")
123
+ } else if (isTestnet(rpcUrl)) {
124
+ showMessage("Testnet Mode", "Running on Testnet", "warning")
125
+ }
126
+ }
127
+
120
128
  export const showMessage = (
121
129
  titleMessage = "",
122
130
  contentMessage = "",
@@ -167,16 +175,16 @@ export const getMetaplexInstance = (
167
175
  const bucketPlugin = awsStorage(awsClient, s3Mgr.s3Config.bucketName);
168
176
  metaplex.use(bucketPlugin);
169
177
  } else {
170
- const bundlrStorageDriver = isDevnet
171
- ? new BundlrStorageDriver(metaplex, {
178
+ const irysStorageDriver = isDevnet
179
+ ? new IrysStorageDriver(metaplex, {
172
180
  address: "https://turbo.ardrive.dev",
173
181
  providerUrl: Constants.DEFAULT_RPC_DEVNET,
174
182
  })
175
- : new BundlrStorageDriver(metaplex, {
183
+ : new IrysStorageDriver(metaplex, {
176
184
  address: "https://turbo.ardrive.io",
177
185
  });
178
186
 
179
- metaplex.storage().setDriver(bundlrStorageDriver);
187
+ metaplex.storage().setDriver(irysStorageDriver);
180
188
  }
181
189
 
182
190
  metaplex.storage().setDriver(