@solana-mobile/dapp-store-cli 0.9.4 → 0.10.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.
package/src/CliSetup.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  checkForSelfUpdate,
12
12
  checkSubmissionNetwork,
13
13
  Constants,
14
+ alphaAppSubmissionMessage,
14
15
  dryRunSuccessMessage,
15
16
  generateNetworkSuffix,
16
17
  parseKeypair,
@@ -50,6 +51,7 @@ function resolveBuildToolsPath(buildToolsPath: string | undefined) {
50
51
  */
51
52
  function latestReleaseMessage() {
52
53
  const messages = [
54
+ `- App details page now supports a Banner Graphic image of size 1200x600px and a Feature Graphic image of size 1200x1200px (optional)`,
53
55
  `- App details page now supports video files. (mp4 file format only and minimum resolution 720p)`,
54
56
  `- 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"`,
55
57
  `- At least 4 screenshots are now required to update or release a new app`,
@@ -303,6 +305,7 @@ publishCommand
303
305
  "-d, --dry-run",
304
306
  "Flag for dry run. Doesn't submit the request to the publisher portal."
305
307
  )
308
+ .option("-l, --alpha", "Flag to mark the submission as alpha test.")
306
309
  .action(
307
310
  async ({
308
311
  appMintAddress,
@@ -312,6 +315,7 @@ publishCommand
312
315
  compliesWithSolanaDappStorePolicies,
313
316
  requestorIsAuthorized,
314
317
  dryRun,
318
+ alpha,
315
319
  }) => {
316
320
  await tryWithErrorMessage(async () => {
317
321
  await checkForSelfUpdate();
@@ -323,6 +327,10 @@ publishCommand
323
327
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
324
328
  }
325
329
 
330
+ if (alpha) {
331
+ alphaAppSubmissionMessage()
332
+ }
333
+
326
334
  const signer = parseKeypair(keypair);
327
335
  if (signer) {
328
336
  if (config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null) {
@@ -335,6 +343,7 @@ publishCommand
335
343
  compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
336
344
  requestorIsAuthorized: requestorIsAuthorized,
337
345
  critical: false,
346
+ alphaTest: alpha,
338
347
  });
339
348
  } else {
340
349
  await publishSubmitCommand({
@@ -345,6 +354,7 @@ publishCommand
345
354
  dryRun: dryRun,
346
355
  compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
347
356
  requestorIsAuthorized: requestorIsAuthorized,
357
+ alphaTest: alpha,
348
358
  });
349
359
  }
350
360
 
@@ -389,6 +399,7 @@ publishCommand
389
399
  "-d, --dry-run",
390
400
  "Flag for dry run. Doesn't submit the request to the publisher portal."
391
401
  )
402
+ .option("-l, --alpha", "Flag to mark the submission as alpha test.")
392
403
  .action(
393
404
  async ({
394
405
  appMintAddress,
@@ -399,6 +410,7 @@ publishCommand
399
410
  requestorIsAuthorized,
400
411
  critical,
401
412
  dryRun,
413
+ alpha,
402
414
  }) => {
403
415
  await tryWithErrorMessage(async () => {
404
416
  await checkForSelfUpdate();
@@ -410,6 +422,10 @@ publishCommand
410
422
  throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
411
423
  }
412
424
 
425
+ if (alpha) {
426
+ alphaAppSubmissionMessage()
427
+ }
428
+
413
429
  const signer = parseKeypair(keypair);
414
430
  if (signer) {
415
431
  await publishUpdateCommand({
@@ -421,6 +437,7 @@ publishCommand
421
437
  compliesWithSolanaDappStorePolicies,
422
438
  requestorIsAuthorized,
423
439
  critical,
440
+ alphaTest: alpha,
424
441
  });
425
442
 
426
443
  if (dryRun) {
package/src/CliUtils.ts CHANGED
@@ -19,7 +19,7 @@ import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
19
19
  import { S3StorageManager } from "./config/index.js";
20
20
 
21
21
  export class Constants {
22
- static CLI_VERSION = "0.9.4";
22
+ static CLI_VERSION = "0.10.0";
23
23
  static CONFIG_FILE_NAME = "config.yaml";
24
24
  static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
25
25
  static DEFAULT_PRIORITY_FEE = 500000;
@@ -143,6 +143,15 @@ export const dryRunSuccessMessage = () => {
143
143
  showMessage("Dry run", "Dry run was successful", "standard")
144
144
  }
145
145
 
146
+ export const alphaAppSubmissionMessage = () => {
147
+ showMessage(
148
+ "Alpha release",
149
+ "Alpha releases are not reviewed on dApp store and are meant for internal testing only.\n" +
150
+ "Run the `npx dapp-store publish submit ...` command again without the `--alpha` param to publish the app",
151
+ "warning"
152
+ )
153
+ }
154
+
146
155
  export const showNetworkWarningIfApplicable = (rpcUrl: string) => {
147
156
  if (isDevnet(rpcUrl)) {
148
157
  showMessage("Devnet Mode", "Running on Devnet", "warning")
@@ -14,6 +14,7 @@ type PublishSubmitCommandInput = {
14
14
  dryRun: boolean;
15
15
  compliesWithSolanaDappStorePolicies: boolean;
16
16
  requestorIsAuthorized: boolean;
17
+ alphaTest?: boolean;
17
18
  };
18
19
 
19
20
  export const publishSubmitCommand = async ({
@@ -24,6 +25,7 @@ export const publishSubmitCommand = async ({
24
25
  dryRun = false,
25
26
  compliesWithSolanaDappStorePolicies = false,
26
27
  requestorIsAuthorized = false,
28
+ alphaTest
27
29
  }: PublishSubmitCommandInput) => {
28
30
  showMessage(
29
31
  `Publishing Estimates`,
@@ -52,6 +54,10 @@ export const publishSubmitCommand = async ({
52
54
  lastUpdatedVersionOnStore: lastUpdatedVersionOnStore,
53
55
  } = await loadPublishDetailsWithChecks();
54
56
 
57
+ if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) {
58
+ throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`)
59
+ }
60
+
55
61
  const sign = ((buf: Buffer) =>
56
62
  nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
57
63
 
@@ -74,12 +80,15 @@ export const publishSubmitCommand = async ({
74
80
  solanaMobileDappPublisherPortalDetails,
75
81
  compliesWithSolanaDappStorePolicies,
76
82
  requestorIsAuthorized,
83
+ alphaTest,
77
84
  },
78
85
  dryRun
79
86
  );
80
87
 
81
- await writeToPublishDetails(
82
- {
83
- lastUpdatedVersionOnStore: { address: releaseAddr }
84
- });
88
+ if (!alphaTest) {
89
+ await writeToPublishDetails(
90
+ {
91
+ lastUpdatedVersionOnStore: { address: releaseAddr }
92
+ });
93
+ }
85
94
  };
@@ -14,6 +14,7 @@ type PublishUpdateCommandInput = {
14
14
  compliesWithSolanaDappStorePolicies: boolean;
15
15
  requestorIsAuthorized: boolean;
16
16
  critical: boolean;
17
+ alphaTest?: boolean;
17
18
  };
18
19
 
19
20
  export const publishUpdateCommand = async ({
@@ -25,6 +26,7 @@ export const publishUpdateCommand = async ({
25
26
  compliesWithSolanaDappStorePolicies = false,
26
27
  requestorIsAuthorized = false,
27
28
  critical = false,
29
+ alphaTest,
28
30
  }: PublishUpdateCommandInput) => {
29
31
 
30
32
  showMessage(
@@ -60,6 +62,10 @@ export const publishUpdateCommand = async ({
60
62
  lastUpdatedVersionOnStore: lastUpdatedVersionOnStore
61
63
  } = await loadPublishDetailsWithChecks();
62
64
 
65
+ if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) {
66
+ throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`)
67
+ }
68
+
63
69
  const sign = ((buf: Buffer) =>
64
70
  nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
65
71
 
@@ -83,11 +89,14 @@ export const publishUpdateCommand = async ({
83
89
  compliesWithSolanaDappStorePolicies,
84
90
  requestorIsAuthorized,
85
91
  criticalUpdate: critical,
92
+ alphaTest,
86
93
  },
87
94
  dryRun
88
95
  );
89
- await writeToPublishDetails(
90
- {
91
- lastUpdatedVersionOnStore: { address: releaseAddr }
92
- });
96
+ if (!alphaTest) {
97
+ await writeToPublishDetails(
98
+ {
99
+ lastUpdatedVersionOnStore: { address: releaseAddr }
100
+ });
101
+ }
93
102
  };
@@ -20,6 +20,7 @@ import util from "util";
20
20
  import { imageSize } from "image-size";
21
21
  import { exec } from "child_process";
22
22
  import getVideoDimensions from "get-video-dimensions";
23
+ import { PublicKey } from "@solana/web3.js";
23
24
 
24
25
  const runImgSize = util.promisify(imageSize);
25
26
  const runExec = util.promisify(exec);
@@ -126,6 +127,24 @@ export const loadPublishDetailsWithChecks = async (
126
127
  throw new Error("Please specify at least one media entry of type icon in your configuration file");
127
128
  }
128
129
 
130
+ const banner = config.release.media?.find(
131
+ (asset: any) => asset.purpose === "banner"
132
+ )?.uri;
133
+
134
+ if (banner) {
135
+ const bannerPath = path.join(process.cwd(), banner);
136
+ await checkBannerCompatibility(bannerPath);
137
+ }
138
+
139
+ const featureGraphic = config.release.media?.find(
140
+ (asset: any) => asset.purpose === "featureGraphic"
141
+ )?.uri;
142
+
143
+ if (featureGraphic) {
144
+ const featureGraphicPath = path.join(process.cwd(), featureGraphic);
145
+ await checkFeatureGraphicCompatibility(featureGraphicPath);
146
+ }
147
+
129
148
  config.release.media.forEach((item: PublishDetails["release"]["media"][0]) => {
130
149
  const mediaPath = path.join(process.cwd(), item.uri);
131
150
  if (!fs.existsSync(mediaPath)) {
@@ -148,7 +167,7 @@ export const loadPublishDetailsWithChecks = async (
148
167
 
149
168
  for (const item of screenshots) {
150
169
  const mediaPath = path.join(process.cwd(), item.uri);
151
- if (await checkScreenshotSize(mediaPath)) {
170
+ if (await checkScreenshotDimensions(mediaPath)) {
152
171
  throw new Error(`Screenshot ${mediaPath} must be at least 1080px in width and height.`);
153
172
  }
154
173
  }
@@ -159,7 +178,7 @@ export const loadPublishDetailsWithChecks = async (
159
178
 
160
179
  for (const video of videos) {
161
180
  const mediaPath = path.join(process.cwd(), video.uri);
162
- if (await checkVideoSize(mediaPath)) {
181
+ if (await checkVideoDimensions(mediaPath)) {
163
182
  throw new Error(`Video ${mediaPath} must be at least 720px in width and height.`);
164
183
  }
165
184
  }
@@ -179,6 +198,21 @@ export const loadPublishDetailsWithChecks = async (
179
198
  }
180
199
  }
181
200
 
201
+ const alpha_testers = config.solana_mobile_dapp_publisher_portal.alpha_testers;
202
+ if (alpha_testers !== undefined) {
203
+ for (const wallet of alpha_testers) {
204
+ try {
205
+ void new PublicKey(wallet.address);
206
+ } catch (e: unknown) {
207
+ throw new Error(`invalid alpha tester wallet address <${wallet}>`);
208
+ }
209
+ }
210
+
211
+ if (alpha_testers.size > 10) {
212
+ throw new Error(`Alpha testers are limited to 10 per app submission`);
213
+ }
214
+ }
215
+
182
216
  return config;
183
217
  };
184
218
 
@@ -192,6 +226,26 @@ const checkIconCompatibility = async (path: string, typeString: string) => {
192
226
  }
193
227
  };
194
228
 
229
+ const checkBannerCompatibility = async (path: string) => {
230
+ if (!fs.existsSync(path) || !checkImageExtension(path)) {
231
+ throw new Error(`Please check the path to your banner image and ensure the file is a jpeg, png, or webp file.`);
232
+ }
233
+
234
+ if (await checkBannerDimensions(path)) {
235
+ throw new Error("Banner must be 1200px by 600px.");
236
+ }
237
+ };
238
+
239
+ const checkFeatureGraphicCompatibility = async (path: string) => {
240
+ if (!fs.existsSync(path) || !checkImageExtension(path)) {
241
+ throw new Error(`Please check the path to your featureGraphic image and ensure the file is a jpeg, png, or webp file.`);
242
+ }
243
+
244
+ if (await checkFeatureGraphicDimensions(path)) {
245
+ throw new Error("Feature Graphic must be 1200px by 1200px.");
246
+ }
247
+ };
248
+
195
249
  const checkImageExtension = (uri: string): boolean => {
196
250
  const fileExt = path.extname(uri).toLowerCase();
197
251
  return (
@@ -241,13 +295,25 @@ const checkIconDimensions = async (iconPath: string): Promise<boolean> => {
241
295
  return size?.width != size?.height || (size?.width ?? 0) != 512;
242
296
  };
243
297
 
244
- const checkScreenshotSize = async (imagePath: string): Promise<boolean> => {
298
+ const checkScreenshotDimensions = async (imagePath: string): Promise<boolean> => {
245
299
  const size = await runImgSize(imagePath);
246
300
 
247
301
  return (size?.width ?? 0) < 1080 || (size?.height ?? 0) < 1080;
248
302
  }
249
303
 
250
- const checkVideoSize = async (imagePath: string): Promise<boolean> => {
304
+ const checkBannerDimensions = async (imagePath: string): Promise<boolean> => {
305
+ const size = await runImgSize(imagePath);
306
+
307
+ return (size?.width ?? 0) != 1200 || (size?.height ?? 0) != 600;
308
+ }
309
+
310
+ const checkFeatureGraphicDimensions = async (imagePath: string): Promise<boolean> => {
311
+ const size = await runImgSize(imagePath);
312
+
313
+ return (size?.width ?? 0) != 1200 || (size?.height ?? 0) != 1200;
314
+ }
315
+
316
+ const checkVideoDimensions = async (imagePath: string): Promise<boolean> => {
251
317
  const size = await getVideoDimensions(imagePath);
252
318
 
253
319
  return (size?.width ?? 0) < 720 || (size?.height ?? 0) < 720;
@@ -23,6 +23,10 @@ release:
23
23
  media:
24
24
  - purpose: icon
25
25
  uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
26
+ - purpose: banner
27
+ uri: <<RELATIVE_PATH_TO_BANNER>>
28
+ - purpose: featureGraphic
29
+ uri: <<RELATIVE_PATH_TO_FEATURE_GRAPHIC>>
26
30
  - purpose: screenshot
27
31
  uri: <<RELATIVE_PATH_TO_SCREENSHOT1>>
28
32
  - purpose: screenshot
@@ -51,4 +55,9 @@ release:
51
55
  solana_mobile_dapp_publisher_portal:
52
56
  google_store_package: <<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>
53
57
  testing_instructions: >-
54
- <<TESTING_INSTRUCTIONS>>
58
+ <<TESTING_INSTRUCTIONS>>
59
+ alpha_testers:
60
+ - address: <<genesis token wallet address>>
61
+ comment: <<Optional. For internal use only>>
62
+ - address: <<genesis token wallet address>>
63
+ comment: <<Optional. For internal use only>>