@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/lib/CliSetup.js +29 -19
- package/lib/CliUtils.js +9 -6
- package/lib/commands/ValidateCommand.js +3 -3
- package/lib/commands/create/CreateCliApp.js +4 -4
- package/lib/commands/create/CreateCliPublisher.js +4 -4
- package/lib/commands/create/CreateCliRelease.js +4 -4
- package/lib/commands/publish/PublishCliRemove.js +3 -3
- package/lib/commands/publish/PublishCliSubmit.js +16 -6
- package/lib/commands/publish/PublishCliSupport.js +3 -3
- package/lib/commands/publish/PublishCliUpdate.js +16 -6
- package/lib/commands/scaffolding/ScaffoldInit.js +1 -1
- package/lib/config/PublishDetails.js +224 -62
- package/lib/generated/config_obj.json +1 -1
- package/lib/generated/config_schema.json +1 -1
- package/lib/package.json +2 -2
- package/lib/prebuild_schema/publishing_source.yaml +10 -1
- package/lib/prebuild_schema/schemagen.js +4 -4
- package/package.json +2 -2
- package/src/CliSetup.ts +17 -0
- package/src/CliUtils.ts +10 -1
- package/src/commands/publish/PublishCliSubmit.ts +13 -4
- package/src/commands/publish/PublishCliUpdate.ts +13 -4
- package/src/config/PublishDetails.ts +70 -4
- package/src/prebuild_schema/publishing_source.yaml +10 -1
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.
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>>
|