@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.
- package/lib/CliSetup.js +55 -45
- package/lib/CliUtils.js +13 -6
- package/lib/commands/ValidateCommand.js +0 -3
- package/lib/commands/create/CreateCliApp.js +1 -3
- package/lib/commands/create/CreateCliPublisher.js +6 -4
- package/lib/commands/create/CreateCliRelease.js +37 -16
- package/lib/commands/publish/PublishCliSubmit.js +16 -3
- package/lib/commands/publish/PublishCliUpdate.js +16 -3
- package/lib/config/PublishDetails.js +269 -13
- package/lib/generated/config_obj.json +1 -1
- package/lib/package.json +5 -4
- package/lib/prebuild_schema/publishing_source.yaml +9 -1
- package/package.json +5 -4
- package/src/CliSetup.ts +32 -14
- package/src/CliUtils.ts +15 -7
- package/src/commands/ValidateCommand.ts +0 -4
- package/src/commands/create/CreateCliApp.ts +1 -0
- package/src/commands/create/CreateCliPublisher.ts +3 -0
- package/src/commands/create/CreateCliRelease.ts +33 -9
- package/src/commands/publish/PublishCliSubmit.ts +11 -1
- package/src/commands/publish/PublishCliUpdate.ts +10 -1
- package/src/config/PublishDetails.ts +119 -13
- package/src/prebuild_schema/publishing_source.yaml +9 -1
|
@@ -11,7 +11,6 @@ import { debug, showMessage } from "../CliUtils.js";
|
|
|
11
11
|
|
|
12
12
|
import type { Keypair } from "@solana/web3.js";
|
|
13
13
|
import type { MetaplexFile } from "@metaplex-foundation/js";
|
|
14
|
-
import { isMetaplexFile } from "@metaplex-foundation/js";
|
|
15
14
|
import { loadPublishDetailsWithChecks } from "../config/PublishDetails.js";
|
|
16
15
|
|
|
17
16
|
export const validateCommand = async ({
|
|
@@ -37,7 +36,6 @@ export const validateCommand = async ({
|
|
|
37
36
|
|
|
38
37
|
try {
|
|
39
38
|
validatePublisher(publisherJson);
|
|
40
|
-
console.info(`Publisher JSON valid!`);
|
|
41
39
|
} catch (e) {
|
|
42
40
|
const errorMsg = (e as Error | null)?.message ?? "";
|
|
43
41
|
showMessage(
|
|
@@ -56,7 +54,6 @@ export const validateCommand = async ({
|
|
|
56
54
|
|
|
57
55
|
try {
|
|
58
56
|
validateApp(appJson);
|
|
59
|
-
console.info(`App JSON valid!`);
|
|
60
57
|
} catch (e) {
|
|
61
58
|
const errorMsg = (e as Error | null)?.message ?? "";
|
|
62
59
|
showMessage(
|
|
@@ -87,7 +84,6 @@ export const validateCommand = async ({
|
|
|
87
84
|
|
|
88
85
|
try {
|
|
89
86
|
validateRelease(JSON.parse(objStringified));
|
|
90
|
-
console.info(`Release JSON valid!`);
|
|
91
87
|
} catch (e) {
|
|
92
88
|
const errorMsg = (e as Error | null)?.message ?? "";
|
|
93
89
|
showMessage(
|
|
@@ -62,6 +62,7 @@ const createPublisherNft = async (
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
throw new Error("Unable to mint publisher NFT");
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
export const createPublisherCommand = async ({
|
|
@@ -96,4 +97,6 @@ export const createPublisherCommand = async ({
|
|
|
96
97
|
|
|
97
98
|
return { publisherAddress, transactionSignature };
|
|
98
99
|
}
|
|
100
|
+
|
|
101
|
+
return { publisherAddress: "", transactionSignature: "" };
|
|
99
102
|
};
|
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
PublicKey,
|
|
11
11
|
sendAndConfirmTransaction,
|
|
12
12
|
} from "@solana/web3.js";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import { createHash } from "crypto";
|
|
13
15
|
import {
|
|
14
16
|
Constants,
|
|
15
17
|
getMetaplexInstance,
|
|
16
18
|
showMessage
|
|
17
19
|
} from "../../CliUtils.js";
|
|
18
|
-
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
20
|
+
import { PublishDetails, loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
19
21
|
|
|
20
22
|
type CreateReleaseCommandInput = {
|
|
21
23
|
appMintAddress: string;
|
|
@@ -89,6 +91,7 @@ const createReleaseNft = async ({
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
94
|
+
throw new Error("Unable to mint release NFT");
|
|
92
95
|
};
|
|
93
96
|
|
|
94
97
|
export const createReleaseCommand = async ({
|
|
@@ -102,28 +105,49 @@ export const createReleaseCommand = async ({
|
|
|
102
105
|
}: CreateReleaseCommandInput) => {
|
|
103
106
|
const connection = new Connection(url);
|
|
104
107
|
|
|
105
|
-
const
|
|
108
|
+
const config = await loadPublishDetailsWithChecks(buildToolsPath);
|
|
106
109
|
|
|
110
|
+
const apkEntry = config.release.files.find(
|
|
111
|
+
(asset: PublishDetails["release"]["files"][0]) => asset.purpose === "install"
|
|
112
|
+
)!;
|
|
113
|
+
const mediaBuffer = await fs.promises.readFile(apkEntry.uri);
|
|
114
|
+
const hash = createHash("sha256").update(mediaBuffer).digest("base64");
|
|
107
115
|
|
|
108
|
-
if (
|
|
109
|
-
throw new Error(
|
|
116
|
+
if (config.lastSubmittedVersionOnChain != null && hash === config.lastSubmittedVersionOnChain.apk_hash) {
|
|
117
|
+
throw new Error(`The last created release used the same apk file.`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (config.lastSubmittedVersionOnChain != null && config.release.android_details.version_code <= config.lastSubmittedVersionOnChain.version_code) {
|
|
121
|
+
throw new Error(`Each release NFT should have higher version code than previous minted release NFT.\nLast released version code is ${config.lastSubmittedVersionOnChain.version_code}.\nCurrent version code from apk file is ${config.release.android_details.version_code}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (config.app.android_package != config.release.android_details.android_package) {
|
|
125
|
+
throw new Error("App package name and release package name do not match.\nApp release specifies " + config.app.android_package + " while release specifies " + config.release.android_details.android_package)
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
if (!dryRun) {
|
|
113
129
|
const { releaseAddress, transactionSignature } = await createReleaseNft({
|
|
114
|
-
appMintAddress: app.address ?? appMintAddress,
|
|
130
|
+
appMintAddress: config.app.address ?? appMintAddress,
|
|
115
131
|
connection,
|
|
116
132
|
publisher: signer,
|
|
117
133
|
releaseDetails: {
|
|
118
|
-
...release,
|
|
134
|
+
...config.release,
|
|
119
135
|
},
|
|
120
|
-
appDetails: app,
|
|
121
|
-
publisherDetails: publisher,
|
|
136
|
+
appDetails: config.app,
|
|
137
|
+
publisherDetails: config.publisher,
|
|
122
138
|
storageParams: storageParams,
|
|
123
139
|
priorityFeeLamports: priorityFeeLamports,
|
|
124
140
|
});
|
|
125
141
|
|
|
126
|
-
await writeToPublishDetails(
|
|
142
|
+
await writeToPublishDetails(
|
|
143
|
+
{
|
|
144
|
+
release: { address: releaseAddress },
|
|
145
|
+
lastSubmittedVersionOnChain: {
|
|
146
|
+
address: releaseAddress,
|
|
147
|
+
version_code: config.release.android_details.version_code,
|
|
148
|
+
apk_hash: hash,
|
|
149
|
+
}
|
|
150
|
+
});
|
|
127
151
|
|
|
128
152
|
return { releaseAddress, transactionSignature };
|
|
129
153
|
}
|
|
@@ -4,7 +4,7 @@ import { publishSubmit } from "@solana-mobile/dapp-store-publishing-tools";
|
|
|
4
4
|
import nacl from "tweetnacl";
|
|
5
5
|
import { checkMintedStatus, showMessage } from "../../CliUtils.js";
|
|
6
6
|
import { Buffer } from "buffer";
|
|
7
|
-
import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
|
|
7
|
+
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
8
8
|
|
|
9
9
|
type PublishSubmitCommandInput = {
|
|
10
10
|
appMintAddress: string;
|
|
@@ -49,6 +49,7 @@ export const publishSubmitCommand = async ({
|
|
|
49
49
|
app: appDetails,
|
|
50
50
|
release: releaseDetails,
|
|
51
51
|
solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
|
|
52
|
+
lastUpdatedVersionOnStore: lastUpdatedVersionOnStore,
|
|
52
53
|
} = await loadPublishDetailsWithChecks();
|
|
53
54
|
|
|
54
55
|
const sign = ((buf: Buffer) =>
|
|
@@ -58,6 +59,10 @@ export const publishSubmitCommand = async ({
|
|
|
58
59
|
const appAddr = appMintAddress ?? appDetails.address;
|
|
59
60
|
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
60
61
|
|
|
62
|
+
if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
|
|
63
|
+
throw new Error(`You've already submitted this version for review.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
|
|
62
67
|
|
|
63
68
|
await publishSubmit(
|
|
@@ -72,4 +77,9 @@ export const publishSubmitCommand = async ({
|
|
|
72
77
|
},
|
|
73
78
|
dryRun
|
|
74
79
|
);
|
|
80
|
+
|
|
81
|
+
await writeToPublishDetails(
|
|
82
|
+
{
|
|
83
|
+
lastUpdatedVersionOnStore: { address: releaseAddr }
|
|
84
|
+
});
|
|
75
85
|
};
|
|
@@ -3,7 +3,7 @@ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publish
|
|
|
3
3
|
import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
4
|
import { checkMintedStatus, showMessage } from "../../CliUtils.js";
|
|
5
5
|
import nacl from "tweetnacl";
|
|
6
|
-
import { loadPublishDetailsWithChecks } from "../../config/PublishDetails.js";
|
|
6
|
+
import { loadPublishDetailsWithChecks, writeToPublishDetails } from "../../config/PublishDetails.js";
|
|
7
7
|
|
|
8
8
|
type PublishUpdateCommandInput = {
|
|
9
9
|
appMintAddress: string;
|
|
@@ -51,6 +51,7 @@ export const publishUpdateCommand = async ({
|
|
|
51
51
|
app: appDetails,
|
|
52
52
|
release: releaseDetails,
|
|
53
53
|
solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
|
|
54
|
+
lastUpdatedVersionOnStore: lastUpdatedVersionOnStore
|
|
54
55
|
} = await loadPublishDetailsWithChecks();
|
|
55
56
|
|
|
56
57
|
const sign = ((buf: Buffer) =>
|
|
@@ -60,6 +61,10 @@ export const publishUpdateCommand = async ({
|
|
|
60
61
|
const appAddr = appMintAddress ?? appDetails.address;
|
|
61
62
|
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
62
63
|
|
|
64
|
+
if (lastUpdatedVersionOnStore != null && releaseAddr === lastUpdatedVersionOnStore.address) {
|
|
65
|
+
throw new Error(`You've already submitted this version for review.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
|
|
64
69
|
|
|
65
70
|
await publishUpdate(
|
|
@@ -75,4 +80,8 @@ export const publishUpdateCommand = async ({
|
|
|
75
80
|
},
|
|
76
81
|
dryRun
|
|
77
82
|
);
|
|
83
|
+
await writeToPublishDetails(
|
|
84
|
+
{
|
|
85
|
+
lastUpdatedVersionOnStore: { address: releaseAddr }
|
|
86
|
+
});
|
|
78
87
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AndroidDetails,
|
|
3
3
|
App,
|
|
4
|
+
LastSubmittedVersionOnChain,
|
|
5
|
+
LastUpdatedVersionOnStore,
|
|
4
6
|
Publisher,
|
|
5
7
|
Release,
|
|
6
8
|
SolanaMobileDappPublisherPortal
|
|
@@ -16,6 +18,7 @@ import { Constants, showMessage } from "../CliUtils.js";
|
|
|
16
18
|
import util from "util";
|
|
17
19
|
import { imageSize } from "image-size";
|
|
18
20
|
import { exec } from "child_process";
|
|
21
|
+
import getVideoDimensions from "get-video-dimensions";
|
|
19
22
|
|
|
20
23
|
const runImgSize = util.promisify(imageSize);
|
|
21
24
|
const runExec = util.promisify(exec);
|
|
@@ -25,6 +28,8 @@ export interface PublishDetails {
|
|
|
25
28
|
app: App;
|
|
26
29
|
release: Release;
|
|
27
30
|
solana_mobile_dapp_publisher_portal: SolanaMobileDappPublisherPortal;
|
|
31
|
+
lastSubmittedVersionOnChain: LastSubmittedVersionOnChain
|
|
32
|
+
lastUpdatedVersionOnStore: LastUpdatedVersionOnStore,
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const AaptPrefixes = {
|
|
@@ -42,6 +47,8 @@ type SaveToConfigArgs = {
|
|
|
42
47
|
publisher?: Pick<Publisher, "address">;
|
|
43
48
|
app?: Pick<App, "address">;
|
|
44
49
|
release?: Pick<Release, "address">;
|
|
50
|
+
lastSubmittedVersionOnChain?: LastSubmittedVersionOnChain;
|
|
51
|
+
lastUpdatedVersionOnStore?: LastUpdatedVersionOnStore;
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
const ajv = new Ajv({ strictTuples: false });
|
|
@@ -119,9 +126,17 @@ export const loadPublishDetailsWithChecks = async (
|
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
config.release.media.forEach((item: PublishDetails["release"]["media"][0]) => {
|
|
122
|
-
const
|
|
123
|
-
if (!fs.existsSync(
|
|
124
|
-
throw new Error(`
|
|
129
|
+
const mediaPath = path.join(process.cwd(), item.uri);
|
|
130
|
+
if (!fs.existsSync(mediaPath)) {
|
|
131
|
+
throw new Error(`File doesnt exist: ${item.uri}.`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (item.purpose == "screenshot" && !checkImageExtension(mediaPath)) {
|
|
135
|
+
throw new Error(`Please ensure the file ${item.uri} is a jpeg, png, or webp file.`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (item.purpose == "video" && !checkVideoExtension(mediaPath)) {
|
|
139
|
+
throw new Error(`Please ensure the file ${item.uri} is a mp4.`)
|
|
125
140
|
}
|
|
126
141
|
}
|
|
127
142
|
);
|
|
@@ -130,12 +145,26 @@ export const loadPublishDetailsWithChecks = async (
|
|
|
130
145
|
(asset: any) => asset.purpose === "screenshot"
|
|
131
146
|
)
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
for (const item of screenshots) {
|
|
149
|
+
const mediaPath = path.join(process.cwd(), item.uri);
|
|
150
|
+
if (await checkScreenshotSize(mediaPath)) {
|
|
151
|
+
throw new Error(`Screenshot ${mediaPath} must be at least 1080px in width and height.`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const videos = config.release.media?.filter(
|
|
156
|
+
(asset: any) => asset.purpose === "video"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
for (const video of videos) {
|
|
160
|
+
const mediaPath = path.join(process.cwd(), video.uri);
|
|
161
|
+
if (await checkVideoSize(mediaPath)) {
|
|
162
|
+
throw new Error(`Video ${mediaPath} must be at least 720px in width and height.`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (screenshots.length + videos.length < 4) {
|
|
167
|
+
throw new Error(`At least 4 screenshots or videos are required for publishing a new release. Found only ${screenshots.length + videos.length}`)
|
|
139
168
|
}
|
|
140
169
|
|
|
141
170
|
validateLocalizableResources(config);
|
|
@@ -172,6 +201,13 @@ const checkImageExtension = (uri: string): boolean => {
|
|
|
172
201
|
);
|
|
173
202
|
};
|
|
174
203
|
|
|
204
|
+
const checkVideoExtension = (uri: string): boolean => {
|
|
205
|
+
const fileExt = path.extname(uri).toLowerCase();
|
|
206
|
+
return (
|
|
207
|
+
fileExt == ".mp4"
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
175
211
|
/**
|
|
176
212
|
* We need to pre-check some things in the localized resources before we move forward
|
|
177
213
|
*/
|
|
@@ -204,6 +240,19 @@ const checkIconDimensions = async (iconPath: string): Promise<boolean> => {
|
|
|
204
240
|
return size?.width != size?.height || (size?.width ?? 0) != 512;
|
|
205
241
|
};
|
|
206
242
|
|
|
243
|
+
const checkScreenshotSize = async (imagePath: string): Promise<boolean> => {
|
|
244
|
+
const size = await runImgSize(imagePath);
|
|
245
|
+
|
|
246
|
+
return (size?.width ?? 0) < 1080 || (size?.height ?? 0) < 1080;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const checkVideoSize = async (imagePath: string): Promise<boolean> => {
|
|
250
|
+
const size = await getVideoDimensions(imagePath);
|
|
251
|
+
|
|
252
|
+
return (size?.width ?? 0) < 720 || (size?.height ?? 0) < 720;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
207
256
|
const getAndroidDetails = async (
|
|
208
257
|
aaptDir: string,
|
|
209
258
|
apkPath: string
|
|
@@ -223,7 +272,7 @@ const getAndroidDetails = async (
|
|
|
223
272
|
const minSdk = new RegExp(
|
|
224
273
|
AaptPrefixes.sdkPrefix + AaptPrefixes.quoteRegex
|
|
225
274
|
).exec(stdout);
|
|
226
|
-
const permissions = [...stdout.matchAll(/uses-permission: name='(.*)'/g)];
|
|
275
|
+
const permissions = [...stdout.matchAll(/uses-permission: name='(.*)'/g)].flatMap(permission => permission[1]);
|
|
227
276
|
const locales = new RegExp(
|
|
228
277
|
AaptPrefixes.localePrefix + AaptPrefixes.quoteNonLazyRegex
|
|
229
278
|
).exec(stdout);
|
|
@@ -241,6 +290,30 @@ const getAndroidDetails = async (
|
|
|
241
290
|
localeArray = ["en-US"].concat(localesSrc.split("' '").slice(1));
|
|
242
291
|
}
|
|
243
292
|
|
|
293
|
+
if (permissions.includes("android.permission.INSTALL_PACKAGES") || permissions.includes("android.permission.DELETE_PACKAGES")) {
|
|
294
|
+
showMessage(
|
|
295
|
+
"App requests system app install/delete permission",
|
|
296
|
+
"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.",
|
|
297
|
+
"error"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (permissions.includes("android.permission.REQUEST_INSTALL_PACKAGES") || permissions.includes("android.permission.REQUEST_DELETE_PACKAGES")) {
|
|
302
|
+
showMessage(
|
|
303
|
+
"App requests install or delete permission",
|
|
304
|
+
"App will be subject to additional security reviews for listing on Solana dApp Store and processing time may be beyond regular review time",
|
|
305
|
+
"warning"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (permissions.includes("com.solanamobile.seedvault.ACCESS_SEED_VAULT")) {
|
|
310
|
+
showMessage(
|
|
311
|
+
"App requests Seed Vault permission",
|
|
312
|
+
"If this is not a wallet application, your app maybe rejected from listing on Solana dApp Store.",
|
|
313
|
+
"warning"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
244
317
|
if (localeArray.length >= 60) {
|
|
245
318
|
showMessage(
|
|
246
319
|
"The bundle apk claims supports for following locales",
|
|
@@ -252,13 +325,15 @@ const getAndroidDetails = async (
|
|
|
252
325
|
);
|
|
253
326
|
}
|
|
254
327
|
|
|
328
|
+
checkAbis(apkPath);
|
|
329
|
+
|
|
255
330
|
return {
|
|
256
331
|
android_package: appPackage?.[1] ?? "",
|
|
257
332
|
min_sdk: parseInt(minSdk?.[1] ?? "0", 10),
|
|
258
333
|
version_code: parseInt(versionCode?.[1] ?? "0", 10),
|
|
259
334
|
version: versionName?.[1] ?? "0",
|
|
260
335
|
cert_fingerprint: await extractCertFingerprint(aaptDir, apkPath),
|
|
261
|
-
permissions: permissions
|
|
336
|
+
permissions: permissions,
|
|
262
337
|
locales: localeArray
|
|
263
338
|
};
|
|
264
339
|
} catch (e) {
|
|
@@ -270,6 +345,35 @@ const getAndroidDetails = async (
|
|
|
270
345
|
}
|
|
271
346
|
};
|
|
272
347
|
|
|
348
|
+
const checkAbis = async (apkPath: string) => {
|
|
349
|
+
try {
|
|
350
|
+
const { stdout } = await runExec(`zipinfo -s ${apkPath} | grep \.so$`);
|
|
351
|
+
const amV7libs = [...stdout.matchAll(/lib\/armeabi-v7a\/(.*)/g)].flatMap(permission => permission[1]);
|
|
352
|
+
const x86libs = [...stdout.matchAll(/lib\/x86\/(.*)/g)].flatMap(permission => permission[1]);
|
|
353
|
+
const x8664libs = [...stdout.matchAll(/lib\/x86_64\/(.*)/g)].flatMap(permission => permission[1]);
|
|
354
|
+
if (amV7libs.length > 0 || x86libs.length > 0 || x8664libs.length > 0) {
|
|
355
|
+
|
|
356
|
+
const messages = [
|
|
357
|
+
`Solana dApp Store only supports arm64-v8a abi.`,
|
|
358
|
+
`Your apk file contains following unsupported abis`,
|
|
359
|
+
... amV7libs.length > 0 ? [`\narmeabi-v7a:\n` + amV7libs] : [],
|
|
360
|
+
... x86libs.length > 0 ? [`\nx86:\n` + x86libs] : [],
|
|
361
|
+
... x8664libs.length > 0 ? [`\nx86_64:\n` + x8664libs] : [],
|
|
362
|
+
`\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.`,
|
|
363
|
+
`\n\nSee https://developer.android.com/games/optimize/64-bit#build-with-64-bit for how to optimize your app.`,
|
|
364
|
+
].join('\n')
|
|
365
|
+
|
|
366
|
+
showMessage(
|
|
367
|
+
`Unsupported files found in apk`,
|
|
368
|
+
messages,
|
|
369
|
+
`warning`
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
} catch (e) {
|
|
373
|
+
// Ignore this error.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
273
377
|
export const extractCertFingerprint = async (aaptDir: string, apkPath: string): Promise<string> => {
|
|
274
378
|
const { stdout } = await runExec(`${aaptDir}/apksigner verify --print-certs -v "${apkPath}"`);
|
|
275
379
|
|
|
@@ -283,7 +387,7 @@ export const extractCertFingerprint = async (aaptDir: string, apkPath: string):
|
|
|
283
387
|
}
|
|
284
388
|
}
|
|
285
389
|
|
|
286
|
-
export const writeToPublishDetails = async ({ publisher, app, release }: SaveToConfigArgs) => {
|
|
390
|
+
export const writeToPublishDetails = async ({ publisher, app, release, lastSubmittedVersionOnChain, lastUpdatedVersionOnStore }: SaveToConfigArgs) => {
|
|
287
391
|
const currentConfig = await loadPublishDetailsWithChecks();
|
|
288
392
|
|
|
289
393
|
delete currentConfig.publisher.icon;
|
|
@@ -302,7 +406,9 @@ export const writeToPublishDetails = async ({ publisher, app, release }: SaveToC
|
|
|
302
406
|
...currentConfig.release,
|
|
303
407
|
address: release?.address ?? currentConfig.release.address
|
|
304
408
|
},
|
|
305
|
-
solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal
|
|
409
|
+
solana_mobile_dapp_publisher_portal: currentConfig.solana_mobile_dapp_publisher_portal,
|
|
410
|
+
lastSubmittedVersionOnChain: lastSubmittedVersionOnChain ?? currentConfig.lastSubmittedVersionOnChain,
|
|
411
|
+
lastUpdatedVersionOnStore: lastUpdatedVersionOnStore ?? currentConfig.lastUpdatedVersionOnStore
|
|
306
412
|
};
|
|
307
413
|
|
|
308
414
|
fs.writeFileSync(Constants.getConfigFilePath(), dump(newConfig, {
|
|
@@ -24,7 +24,15 @@ release:
|
|
|
24
24
|
- purpose: icon
|
|
25
25
|
uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
|
|
26
26
|
- purpose: screenshot
|
|
27
|
-
uri: <<
|
|
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>>
|