@solana-mobile/dapp-store-cli 0.1.9 → 0.3.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/README.md +18 -53
- package/lib/esm/commands/create/app.js +2 -2
- package/lib/esm/commands/create/app.js.map +1 -1
- package/lib/esm/commands/create/publisher.js +2 -2
- package/lib/esm/commands/create/publisher.js.map +1 -1
- package/lib/esm/commands/create/release.js +2 -2
- package/lib/esm/commands/create/release.js.map +1 -1
- package/lib/esm/commands/publish/remove.js +6 -2
- package/lib/esm/commands/publish/remove.js.map +1 -1
- package/lib/esm/commands/publish/submit.js +8 -4
- package/lib/esm/commands/publish/submit.js.map +1 -1
- package/lib/esm/commands/publish/support.js +6 -2
- package/lib/esm/commands/publish/support.js.map +1 -1
- package/lib/esm/commands/publish/update.js +6 -2
- package/lib/esm/commands/publish/update.js.map +1 -1
- package/lib/esm/commands/scaffolding/index.js +2 -0
- package/lib/esm/commands/scaffolding/index.js.map +1 -0
- package/lib/esm/commands/scaffolding/init.js +15 -0
- package/lib/esm/commands/scaffolding/init.js.map +1 -0
- package/lib/esm/commands/validate.js +6 -15
- package/lib/esm/commands/validate.js.map +1 -1
- package/lib/esm/config/index.js +1 -2
- package/lib/esm/config/index.js.map +1 -1
- package/lib/esm/generated/config_obj.json +1 -0
- package/lib/esm/generated/config_schema.json +1 -0
- package/lib/esm/index.js +38 -17
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/package.json +6 -3
- package/lib/esm/utils.js +63 -23
- package/lib/esm/utils.js.map +1 -1
- package/lib/types/commands/create/app.d.ts +1 -1
- package/lib/types/commands/create/app.d.ts.map +1 -1
- package/lib/types/commands/create/release.d.ts +1 -1
- package/lib/types/commands/create/release.d.ts.map +1 -1
- package/lib/types/commands/publish/remove.d.ts +1 -1
- package/lib/types/commands/publish/remove.d.ts.map +1 -1
- package/lib/types/commands/publish/submit.d.ts +1 -1
- package/lib/types/commands/publish/submit.d.ts.map +1 -1
- package/lib/types/commands/publish/support.d.ts +1 -1
- package/lib/types/commands/publish/support.d.ts.map +1 -1
- package/lib/types/commands/publish/update.d.ts +1 -1
- package/lib/types/commands/publish/update.d.ts.map +1 -1
- package/lib/types/commands/scaffolding/index.d.ts +2 -0
- package/lib/types/commands/scaffolding/index.d.ts.map +1 -0
- package/lib/types/commands/scaffolding/init.d.ts +2 -0
- package/lib/types/commands/scaffolding/init.d.ts.map +1 -0
- package/lib/types/commands/validate.d.ts.map +1 -1
- package/lib/types/config/index.d.ts.map +1 -1
- package/lib/types/upload/CachedStorageDriver.d.ts +3 -3
- package/lib/types/upload/CachedStorageDriver.d.ts.map +1 -1
- package/lib/types/utils.d.ts +8 -3
- package/lib/types/utils.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/commands/create/app.ts +2 -2
- package/src/commands/create/publisher.ts +2 -2
- package/src/commands/create/release.ts +2 -2
- package/src/commands/publish/remove.ts +9 -2
- package/src/commands/publish/submit.ts +13 -5
- package/src/commands/publish/support.ts +9 -2
- package/src/commands/publish/update.ts +9 -2
- package/src/commands/scaffolding/index.ts +1 -0
- package/src/commands/scaffolding/init.ts +19 -0
- package/src/commands/validate.ts +7 -14
- package/src/config/index.ts +1 -2
- package/src/generated/config_obj.json +1 -0
- package/src/generated/config_schema.json +1 -0
- package/src/index.ts +54 -19
- package/src/prebuild_schema/publishing_source.yaml +46 -0
- package/src/prebuild_schema/schemagen.js +27 -0
- package/src/utils.ts +80 -28
- package/lib/esm/config/schema.json +0 -195
- package/src/config/schema.json +0 -195
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Connection, Keypair } from "@solana/web3.js";
|
|
2
2
|
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
3
|
import { publishSupport } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import {
|
|
4
|
+
import { checkMintedStatus, getConfigWithChecks } from "../../utils.js";
|
|
5
5
|
import nacl from "tweetnacl";
|
|
6
6
|
|
|
7
7
|
type PublishSupportCommandInput = {
|
|
@@ -35,10 +35,17 @@ export const publishSupportCommand = async ({
|
|
|
35
35
|
publisher: publisherDetails,
|
|
36
36
|
app: appDetails,
|
|
37
37
|
release: releaseDetails,
|
|
38
|
-
} = await
|
|
38
|
+
} = await getConfigWithChecks();
|
|
39
|
+
|
|
39
40
|
const sign = ((buf: Buffer) =>
|
|
40
41
|
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
41
42
|
|
|
43
|
+
const pubAddr = publisherDetails.address;
|
|
44
|
+
const appAddr = appMintAddress ?? appDetails.address;
|
|
45
|
+
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
46
|
+
|
|
47
|
+
await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
|
|
48
|
+
|
|
42
49
|
await publishSupport(
|
|
43
50
|
{ connection, sign },
|
|
44
51
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Connection, Keypair } from "@solana/web3.js";
|
|
2
2
|
import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
3
|
import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
|
|
4
|
-
import {
|
|
4
|
+
import { checkMintedStatus, getConfigWithChecks } from "../../utils.js";
|
|
5
5
|
import nacl from "tweetnacl";
|
|
6
6
|
|
|
7
7
|
type PublishUpdateCommandInput = {
|
|
@@ -43,10 +43,17 @@ export const publishUpdateCommand = async ({
|
|
|
43
43
|
app: appDetails,
|
|
44
44
|
release: releaseDetails,
|
|
45
45
|
solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
|
|
46
|
-
} = await
|
|
46
|
+
} = await getConfigWithChecks();
|
|
47
|
+
|
|
47
48
|
const sign = ((buf: Buffer) =>
|
|
48
49
|
nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
|
|
49
50
|
|
|
51
|
+
const pubAddr = publisherDetails.address;
|
|
52
|
+
const appAddr = appMintAddress ?? appDetails.address;
|
|
53
|
+
const releaseAddr = releaseMintAddress ?? releaseDetails.address;
|
|
54
|
+
|
|
55
|
+
await checkMintedStatus(connection, pubAddr, appAddr, releaseAddr);
|
|
56
|
+
|
|
50
57
|
await publishUpdate(
|
|
51
58
|
{ connection, sign },
|
|
52
59
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./init.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import yaml, { dump } from "js-yaml";
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line require-extensions/require-extensions
|
|
4
|
+
import releaseSchema from "../../generated/config_obj.json" assert { type: "json" };
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { Constants } from "../../utils.js";
|
|
7
|
+
|
|
8
|
+
export const initScaffold = (): string => {
|
|
9
|
+
const outputYaml = Constants.CONFIG_FILE_NAME;
|
|
10
|
+
const outFile = `${process.cwd()}/${outputYaml}`;
|
|
11
|
+
|
|
12
|
+
if (fs.existsSync(outFile)) {
|
|
13
|
+
throw Error("Configuration file already present; please use to intialize a new config file.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fs.writeFileSync(outFile, dump(releaseSchema));
|
|
17
|
+
|
|
18
|
+
return `Your configuration file was created: ${outputYaml}`;
|
|
19
|
+
};
|
package/src/commands/validate.ts
CHANGED
|
@@ -5,23 +5,14 @@ import {
|
|
|
5
5
|
validateApp,
|
|
6
6
|
validatePublisher,
|
|
7
7
|
validateRelease,
|
|
8
|
+
metaplexFileReplacer,
|
|
8
9
|
} from "@solana-mobile/dapp-store-publishing-tools";
|
|
9
|
-
import { debug,
|
|
10
|
+
import { debug, getConfigWithChecks } from "../utils.js";
|
|
10
11
|
|
|
11
12
|
import type { Keypair } from "@solana/web3.js";
|
|
12
13
|
import type { MetaplexFile } from "@metaplex-foundation/js";
|
|
13
14
|
import { isMetaplexFile } from "@metaplex-foundation/js";
|
|
14
15
|
|
|
15
|
-
const metaplexFileReplacer = (k: any, v: any) => {
|
|
16
|
-
if (isMetaplexFile(v)) {
|
|
17
|
-
return {
|
|
18
|
-
...v,
|
|
19
|
-
buffer: "(suppressed)",
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
return v;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
16
|
export const validateCommand = async ({
|
|
26
17
|
signer,
|
|
27
18
|
buildToolsPath,
|
|
@@ -33,7 +24,7 @@ export const validateCommand = async ({
|
|
|
33
24
|
publisher: publisherDetails,
|
|
34
25
|
app: appDetails,
|
|
35
26
|
release: releaseDetails,
|
|
36
|
-
} = await
|
|
27
|
+
} = await getConfigWithChecks(buildToolsPath);
|
|
37
28
|
|
|
38
29
|
debug({ publisherDetails, appDetails, releaseDetails });
|
|
39
30
|
|
|
@@ -67,10 +58,12 @@ export const validateCommand = async ({
|
|
|
67
58
|
{ releaseDetails, appDetails, publisherDetails },
|
|
68
59
|
signer.publicKey
|
|
69
60
|
);
|
|
70
|
-
|
|
61
|
+
|
|
62
|
+
const objStringified = JSON.stringify(releaseJson, metaplexFileReplacer, 2);
|
|
63
|
+
debug("releaseJson=", objStringified);
|
|
71
64
|
|
|
72
65
|
try {
|
|
73
|
-
validateRelease(
|
|
66
|
+
validateRelease(JSON.parse(objStringified));
|
|
74
67
|
console.info(`Release JSON valid!`);
|
|
75
68
|
} catch (e) {
|
|
76
69
|
console.error(e);
|
package/src/config/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { load } from "js-yaml";
|
|
|
10
10
|
import Ajv from "ajv";
|
|
11
11
|
|
|
12
12
|
// eslint-disable-next-line require-extensions/require-extensions
|
|
13
|
-
import schemaJson from "
|
|
13
|
+
import schemaJson from "../generated/config_schema.json" assert { type: "json" };
|
|
14
14
|
|
|
15
15
|
// TODO: Add version number return here
|
|
16
16
|
export interface CLIConfig {
|
|
@@ -25,7 +25,6 @@ const validate = ajv.compile(schemaJson);
|
|
|
25
25
|
|
|
26
26
|
export const getConfig = async (configPath: string) => {
|
|
27
27
|
const configFile = await fs.readFile(configPath, "utf-8");
|
|
28
|
-
const configJson = load(configFile);
|
|
29
28
|
|
|
30
29
|
const valid = validate(load(configFile) as object);
|
|
31
30
|
|
|
@@ -0,0 +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>>\n","short_description":"<<SHORT_APP_DESCRIPTION>>\n","long_description":"<<LONG_APP_DESCRIPTION>>\n","new_in_version":"<<WHATS_NEW_IN_THIS_VERSION>>\n","saga_features":"<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>\n"}}},"solana_mobile_dapp_publisher_portal":{"google_store_package":"<<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>","testing_instructions":"<<TESTING_INSTRUCTIONS>>\n"}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"object","properties":{"publisher":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":"string"},"website":{"type":"string"},"email":{"type":"string"},"media":{"type":"array","items":{"type":"object","properties":{"purpose":{"type":"string"},"uri":{"type":"string"}}}}}},"app":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":"string"},"android_package":{"type":"string"},"urls":{"type":"object","properties":{"license_url":{"type":"string"},"copyright_url":{"type":"string"},"privacy_policy_url":{"type":"string"},"website":{"type":"string"}}},"media":{"type":"array","items":{"type":"object","properties":{"purpose":{"type":"string"},"uri":{"type":"string"}}}}}},"release":{"type":"object","properties":{"address":{"type":"string"},"media":{"type":"array","items":{"type":"object","properties":{"purpose":{"type":"string"},"uri":{"type":"string"}},"required":["purpose","uri"]}},"files":{"type":"array","items":{"type":"object","properties":{"purpose":{"type":"string"},"uri":{"type":"string"}}}},"catalog":{"type":"object","properties":{"en-US":{"type":"object","properties":{"name":{"type":"string"},"short_description":{"type":"string"},"long_description":{"type":"string"},"new_in_version":{"type":"string"},"saga_features":{"type":"string"}},"required":["short_description"]}}}}},"solana_mobile_dapp_publisher_portal":{"type":"object","properties":{"google_store_package":{"type":"string"},"testing_instructions":{"type":"string"}}}}}
|
package/src/index.ts
CHANGED
|
@@ -7,11 +7,20 @@ import {
|
|
|
7
7
|
publishSupportCommand,
|
|
8
8
|
publishUpdateCommand
|
|
9
9
|
} from "./commands/publish/index.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
checkForSelfUpdate,
|
|
12
|
+
checkSubmissionNetwork,
|
|
13
|
+
Constants,
|
|
14
|
+
generateNetworkSuffix,
|
|
15
|
+
getConfigWithChecks,
|
|
16
|
+
parseKeypair,
|
|
17
|
+
showMessage
|
|
18
|
+
} from "./utils.js";
|
|
11
19
|
import terminalLink from "terminal-link";
|
|
12
20
|
import boxen from "boxen";
|
|
13
21
|
|
|
14
22
|
import * as dotenv from "dotenv";
|
|
23
|
+
import { initScaffold } from "./commands/scaffolding/index.js";
|
|
15
24
|
|
|
16
25
|
dotenv.config();
|
|
17
26
|
|
|
@@ -36,25 +45,47 @@ function resolveBuildToolsPath(buildToolsPath: string | undefined) {
|
|
|
36
45
|
return;
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
/**
|
|
49
|
+
* This method should be updated with each new release of the CLI, and just do nothing when there isn't anything to report
|
|
50
|
+
*/
|
|
51
|
+
function latestReleaseMessage() {
|
|
52
|
+
showMessage(
|
|
53
|
+
`Publishing Tools Version ${ Constants.CLI_VERSION }`,
|
|
54
|
+
"- The new field \`short_description\` has been added to the set of strings required in the `catalog` section for each locale.\n" +
|
|
55
|
+
"- You can now specify a release-specific icon in the release \`media\` section in your configuration file.", "warning"
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
async function tryWithErrorMessage(block: () => Promise<any>) {
|
|
40
60
|
try {
|
|
41
61
|
await block()
|
|
42
62
|
} catch (e) {
|
|
43
63
|
const errorMsg = (e as Error | null)?.message ?? "";
|
|
44
64
|
|
|
45
|
-
showMessage("Error", errorMsg,
|
|
65
|
+
showMessage("Error", errorMsg, "error");
|
|
46
66
|
}
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
async function main() {
|
|
50
70
|
program
|
|
51
71
|
.name("dapp-store")
|
|
52
|
-
.version(
|
|
53
|
-
.description("CLI to assist with publishing to the Saga Dapp Store")
|
|
72
|
+
.version(Constants.CLI_VERSION)
|
|
73
|
+
.description("CLI to assist with publishing to the Saga Dapp Store")
|
|
74
|
+
|
|
75
|
+
const initCommand = program
|
|
76
|
+
.command("init")
|
|
77
|
+
.description("First-time initialization of tooling configuration")
|
|
78
|
+
.action(async () => {
|
|
79
|
+
tryWithErrorMessage(async () => {
|
|
80
|
+
const msg = initScaffold();
|
|
81
|
+
|
|
82
|
+
showMessage("Initialized", msg);
|
|
83
|
+
})
|
|
84
|
+
});
|
|
54
85
|
|
|
55
86
|
const createCommand = program
|
|
56
87
|
.command("create")
|
|
57
|
-
.description("Create a `publisher`, `app`, or `release`")
|
|
88
|
+
.description("Create a `publisher`, `app`, or `release`")
|
|
58
89
|
|
|
59
90
|
createCommand
|
|
60
91
|
.command("publisher")
|
|
@@ -67,6 +98,7 @@ async function main() {
|
|
|
67
98
|
.option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
|
|
68
99
|
.action(async ({ keypair, url, dryRun }) => {
|
|
69
100
|
tryWithErrorMessage(async () => {
|
|
101
|
+
latestReleaseMessage();
|
|
70
102
|
await checkForSelfUpdate();
|
|
71
103
|
|
|
72
104
|
const signer = parseKeypair(keypair);
|
|
@@ -96,9 +128,10 @@ async function main() {
|
|
|
96
128
|
.option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
|
|
97
129
|
.action(async ({ publisherMintAddress, keypair, url, dryRun }) => {
|
|
98
130
|
tryWithErrorMessage(async () => {
|
|
131
|
+
latestReleaseMessage();
|
|
99
132
|
await checkForSelfUpdate();
|
|
100
133
|
|
|
101
|
-
const config = await
|
|
134
|
+
const config = await getConfigWithChecks();
|
|
102
135
|
|
|
103
136
|
if (!hasAddressInConfig(config.publisher) && !publisherMintAddress) {
|
|
104
137
|
throw new Error("Either specify a publisher mint address in the config file or specify as a CLI argument to this command.")
|
|
@@ -140,6 +173,7 @@ async function main() {
|
|
|
140
173
|
)
|
|
141
174
|
.action(async ({ appMintAddress, keypair, url, dryRun, buildToolsPath }) => {
|
|
142
175
|
tryWithErrorMessage(async () => {
|
|
176
|
+
latestReleaseMessage();
|
|
143
177
|
await checkForSelfUpdate();
|
|
144
178
|
|
|
145
179
|
const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
|
|
@@ -147,7 +181,7 @@ async function main() {
|
|
|
147
181
|
throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
const config = await
|
|
184
|
+
const config = await getConfigWithChecks();
|
|
151
185
|
if (!hasAddressInConfig(config.app) && !appMintAddress) {
|
|
152
186
|
throw new Error("Either specify an app mint address in the config file or specify as a CLI argument to this command")
|
|
153
187
|
}
|
|
@@ -184,6 +218,7 @@ async function main() {
|
|
|
184
218
|
)
|
|
185
219
|
.action(async ({ keypair, buildToolsPath }) => {
|
|
186
220
|
tryWithErrorMessage(async () => {
|
|
221
|
+
latestReleaseMessage();
|
|
187
222
|
await checkForSelfUpdate();
|
|
188
223
|
|
|
189
224
|
const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
|
|
@@ -226,11 +261,11 @@ async function main() {
|
|
|
226
261
|
)
|
|
227
262
|
.option(
|
|
228
263
|
"-a, --app-mint-address <app-mint-address>",
|
|
229
|
-
"The mint address of the app NFT. If not specified, the value from config
|
|
264
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
230
265
|
)
|
|
231
266
|
.option(
|
|
232
267
|
"-r, --release-mint-address <release-mint-address>",
|
|
233
|
-
"The mint address of the release NFT. If not specified, the value from config
|
|
268
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
234
269
|
)
|
|
235
270
|
.option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
|
|
236
271
|
.option(
|
|
@@ -251,7 +286,7 @@ async function main() {
|
|
|
251
286
|
await checkForSelfUpdate();
|
|
252
287
|
await checkSubmissionNetwork(url);
|
|
253
288
|
|
|
254
|
-
const config = await
|
|
289
|
+
const config = await getConfigWithChecks();
|
|
255
290
|
|
|
256
291
|
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
257
292
|
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
@@ -295,11 +330,11 @@ async function main() {
|
|
|
295
330
|
)
|
|
296
331
|
.option(
|
|
297
332
|
"-a, --app-mint-address <app-mint-address>",
|
|
298
|
-
"The mint address of the app NFT. If not specified, the value from config
|
|
333
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
299
334
|
)
|
|
300
335
|
.option(
|
|
301
336
|
"-r, --release-mint-address <release-mint-address>",
|
|
302
|
-
"The mint address of the release NFT. If not specified, the value from config
|
|
337
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
303
338
|
)
|
|
304
339
|
.option("-c, --critical", "Flag for a critical app update request")
|
|
305
340
|
.option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
|
|
@@ -322,7 +357,7 @@ async function main() {
|
|
|
322
357
|
await checkForSelfUpdate();
|
|
323
358
|
await checkSubmissionNetwork(url);
|
|
324
359
|
|
|
325
|
-
const config = await
|
|
360
|
+
const config = await getConfigWithChecks();
|
|
326
361
|
|
|
327
362
|
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
328
363
|
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
@@ -363,11 +398,11 @@ async function main() {
|
|
|
363
398
|
)
|
|
364
399
|
.option(
|
|
365
400
|
"-a, --app-mint-address <app-mint-address>",
|
|
366
|
-
"The mint address of the app NFT. If not specified, the value from config
|
|
401
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
367
402
|
)
|
|
368
403
|
.option(
|
|
369
404
|
"-r, --release-mint-address <release-mint-address>",
|
|
370
|
-
"The mint address of the release NFT. If not specified, the value from config
|
|
405
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
371
406
|
)
|
|
372
407
|
.option("-c, --critical", "Flag for a critical app removal request")
|
|
373
408
|
.option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
|
|
@@ -389,7 +424,7 @@ async function main() {
|
|
|
389
424
|
await checkForSelfUpdate();
|
|
390
425
|
await checkSubmissionNetwork(url);
|
|
391
426
|
|
|
392
|
-
const config = await
|
|
427
|
+
const config = await getConfigWithChecks();
|
|
393
428
|
|
|
394
429
|
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
395
430
|
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
@@ -429,11 +464,11 @@ async function main() {
|
|
|
429
464
|
)
|
|
430
465
|
.option(
|
|
431
466
|
"-a, --app-mint-address <app-mint-address>",
|
|
432
|
-
"The mint address of the app NFT. If not specified, the value from config
|
|
467
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
433
468
|
)
|
|
434
469
|
.option(
|
|
435
470
|
"-r, --release-mint-address <release-mint-address>",
|
|
436
|
-
"The mint address of the release NFT. If not specified, the value from config
|
|
471
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
437
472
|
)
|
|
438
473
|
.option("-u, --url <url>", "RPC URL", "https://devnet.genesysgo.net")
|
|
439
474
|
.option(
|
|
@@ -449,7 +484,7 @@ async function main() {
|
|
|
449
484
|
await checkForSelfUpdate();
|
|
450
485
|
await checkSubmissionNetwork(url);
|
|
451
486
|
|
|
452
|
-
const config = await
|
|
487
|
+
const config = await getConfigWithChecks();
|
|
453
488
|
|
|
454
489
|
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
455
490
|
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
publisher:
|
|
2
|
+
name: <<YOUR_PUBLISHER_NAME>>
|
|
3
|
+
address: ""
|
|
4
|
+
website: <<URL_OF_PUBLISHER_WEBSITE>>
|
|
5
|
+
email: <<EMAIL_ADDRESS_TO_CONTACT_PUBLISHER>>
|
|
6
|
+
media:
|
|
7
|
+
- purpose: icon
|
|
8
|
+
uri: <<RELATIVE_PATH_TO_PUBLISHER_ICON>>
|
|
9
|
+
app:
|
|
10
|
+
name: <<APP_NAME>>
|
|
11
|
+
address: ""
|
|
12
|
+
android_package: <<ANDROID_PACKAGE_NAME>>
|
|
13
|
+
urls:
|
|
14
|
+
license_url: <<URL_OF_APP_LICENSE_OR_TERMS_OF_SERVICE>>
|
|
15
|
+
copyright_url: <<URL_OF_COPYRIGHT_DETAILS_FOR_APP>>
|
|
16
|
+
privacy_policy_url: <<URL_OF_APP_PRIVACY_POLICY>>
|
|
17
|
+
website: <<URL_OF_APP_WEBSITE>>
|
|
18
|
+
media:
|
|
19
|
+
- purpose: icon
|
|
20
|
+
uri: <<RELATIVE_PATH_TO_APP_ICON>>
|
|
21
|
+
release:
|
|
22
|
+
address: ""
|
|
23
|
+
media:
|
|
24
|
+
- purpose: icon
|
|
25
|
+
uri: <<RELATIVE_PATH_TO_RELEASE_ICON>>
|
|
26
|
+
- purpose: screenshot
|
|
27
|
+
uri: <<RELATIVE_PATH_TO_SCREENSHOT>>
|
|
28
|
+
files:
|
|
29
|
+
- purpose: install
|
|
30
|
+
uri: <<RELATIVE_PATH_TO_APK>>
|
|
31
|
+
catalog:
|
|
32
|
+
en-US:
|
|
33
|
+
name: |
|
|
34
|
+
<<APP_NAME>>
|
|
35
|
+
short_description: |
|
|
36
|
+
<<SHORT_APP_DESCRIPTION>>
|
|
37
|
+
long_description: |
|
|
38
|
+
<<LONG_APP_DESCRIPTION>>
|
|
39
|
+
new_in_version: |
|
|
40
|
+
<<WHATS_NEW_IN_THIS_VERSION>>
|
|
41
|
+
saga_features: |
|
|
42
|
+
<<ANY_FEATURES_ONLY_AVAILBLE_WHEN_RUNNING_ON_SAGA>>
|
|
43
|
+
solana_mobile_dapp_publisher_portal:
|
|
44
|
+
google_store_package: <<ANDROID_PACKAGE_NAME_OF_GOOGLE_PLAY_STORE_VERSION_IF_DIFFERENT>>
|
|
45
|
+
testing_instructions: >
|
|
46
|
+
<<TESTING_INSTRUCTIONS>>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs, { read } from "fs";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import generateSchema from "generate-schema";
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
const yamlSrc = fs.readFileSync('./src/prebuild_schema/publishing_source.yaml', 'utf8')
|
|
7
|
+
const convertedYaml = yaml.load(yamlSrc);
|
|
8
|
+
fs.writeFileSync('./src/generated/config_obj.json', Buffer.from(JSON.stringify(convertedYaml)), 'utf-8');
|
|
9
|
+
|
|
10
|
+
const schema = generateSchema.json('result', convertedYaml);
|
|
11
|
+
// CLI 0.3.0: Adding requirement for `short_description` so validation will catch
|
|
12
|
+
schema["properties"]
|
|
13
|
+
["release"]
|
|
14
|
+
["properties"]
|
|
15
|
+
["catalog"]
|
|
16
|
+
["properties"]
|
|
17
|
+
["en-US"].required = ["short_description"];
|
|
18
|
+
|
|
19
|
+
// Generator adds some keys/values we don't need & mess up validation
|
|
20
|
+
delete schema.$schema;
|
|
21
|
+
delete schema.title;
|
|
22
|
+
|
|
23
|
+
const toWrite = Buffer.from(JSON.stringify(schema));
|
|
24
|
+
fs.writeFileSync('./src/generated/config_schema.json', toWrite, 'utf-8');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.log(":: Schema generation step failed ::");
|
|
27
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
|
-
import type { AndroidDetails, App, Publisher, Release } from "@solana-mobile/dapp-store-publishing-tools";
|
|
2
|
+
import type { AndroidDetails, App, Publisher, Release, ReleaseJsonMetadata } from "@solana-mobile/dapp-store-publishing-tools";
|
|
3
3
|
import type { Connection } from "@solana/web3.js";
|
|
4
|
-
import { Keypair } from "@solana/web3.js";
|
|
4
|
+
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
5
5
|
import type { CLIConfig } from "./config/index.js";
|
|
6
6
|
import { getConfig } from "./config/index.js";
|
|
7
7
|
import debugModule from "debug";
|
|
@@ -14,23 +14,45 @@ import { imageSize } from "image-size";
|
|
|
14
14
|
import updateNotifier from "update-notifier";
|
|
15
15
|
import cliPackage from "./package.json" assert { type: "json" };
|
|
16
16
|
import boxen from "boxen";
|
|
17
|
+
import ver from "semver";
|
|
17
18
|
|
|
18
19
|
import { CachedStorageDriver } from "./upload/CachedStorageDriver.js";
|
|
19
20
|
|
|
20
21
|
const runImgSize = util.promisify(imageSize);
|
|
21
22
|
const runExec = util.promisify(exec);
|
|
22
23
|
|
|
24
|
+
export class Constants {
|
|
25
|
+
static CLI_VERSION = "0.3.0";
|
|
26
|
+
static CONFIG_FILE_NAME = "config.yaml";
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export const debug = debugModule("CLI");
|
|
24
30
|
|
|
25
31
|
export const checkForSelfUpdate = async () => {
|
|
26
32
|
const notifier = updateNotifier({ pkg: cliPackage });
|
|
27
33
|
const updateInfo = await notifier.fetchInfo();
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
const latestVer = new ver.SemVer(updateInfo.latest);
|
|
36
|
+
const currentVer = new ver.SemVer(updateInfo.current);
|
|
37
|
+
|
|
38
|
+
if (latestVer.major > currentVer.major || latestVer.minor > currentVer.minor) {
|
|
30
39
|
throw new Error("Please update to the latest version of the dApp Store CLI before proceeding.");
|
|
31
40
|
}
|
|
32
41
|
};
|
|
33
42
|
|
|
43
|
+
export const checkMintedStatus = async (conn: Connection, pubAddr: string, appAddr: string, releaseAddr: string) => {
|
|
44
|
+
const results = await conn.getMultipleAccountsInfo([
|
|
45
|
+
new PublicKey(pubAddr),
|
|
46
|
+
new PublicKey(appAddr),
|
|
47
|
+
new PublicKey(releaseAddr),
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const rentAccounts = results.filter((item) => !(item == undefined) && item?.lamports > 0);
|
|
51
|
+
if (rentAccounts?.length != 3) {
|
|
52
|
+
throw new Error("Please ensure you have minted all of your NFTs before submitting to the Solana Mobile dApp publisher portal.");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
34
56
|
export const parseKeypair = (pathToKeypairFile: string) => {
|
|
35
57
|
try {
|
|
36
58
|
const keypairFile = fs.readFileSync(pathToKeypairFile, "utf-8");
|
|
@@ -53,10 +75,10 @@ const AaptPrefixes = {
|
|
|
53
75
|
localePrefix: "locales: ",
|
|
54
76
|
};
|
|
55
77
|
|
|
56
|
-
export const
|
|
78
|
+
export const getConfigWithChecks = async (
|
|
57
79
|
buildToolsDir: string | null = null
|
|
58
80
|
): Promise<CLIConfig> => {
|
|
59
|
-
const configFilePath = `${process.cwd()}
|
|
81
|
+
const configFilePath = `${process.cwd()}/${Constants.CONFIG_FILE_NAME}`;
|
|
60
82
|
|
|
61
83
|
const config = await getConfig(configFilePath);
|
|
62
84
|
|
|
@@ -79,37 +101,38 @@ export const getConfigFile = async (
|
|
|
79
101
|
const publisherIcon = config.publisher.media?.find(
|
|
80
102
|
(asset: any) => asset.purpose === "icon"
|
|
81
103
|
)?.uri;
|
|
104
|
+
|
|
82
105
|
if (publisherIcon) {
|
|
83
106
|
const iconPath = path.join(process.cwd(), publisherIcon);
|
|
84
|
-
|
|
85
|
-
throw new Error("Please check the path to your Publisher icon and ensure the file is a jpeg, png, or webp file.");
|
|
86
|
-
}
|
|
107
|
+
await checkIconCompatibility(iconPath, "Publisher");
|
|
87
108
|
|
|
88
109
|
const iconBuffer = await fs.promises.readFile(iconPath);
|
|
89
|
-
|
|
90
|
-
if (await checkIconDimensions(iconPath)) {
|
|
91
|
-
throw new Error("Icons must have square dimensions and be no greater than 512px by 512px.")
|
|
92
|
-
}
|
|
93
|
-
|
|
94
110
|
config.publisher.icon = toMetaplexFile(iconBuffer, publisherIcon);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
113
|
const appIcon = config.app.media?.find(
|
|
98
114
|
(asset: any) => asset.purpose === "icon"
|
|
99
115
|
)?.uri;
|
|
116
|
+
|
|
100
117
|
if (appIcon) {
|
|
101
118
|
const iconPath = path.join(process.cwd(), appIcon);
|
|
102
|
-
|
|
103
|
-
throw new Error("Please check the path to your App icon and ensure the file is a jpeg, png, or webp file.")
|
|
104
|
-
}
|
|
119
|
+
await checkIconCompatibility(iconPath, "App");
|
|
105
120
|
|
|
106
121
|
const iconBuffer = await fs.promises.readFile(iconPath);
|
|
122
|
+
config.app.icon = toMetaplexFile(iconBuffer, appIcon);
|
|
123
|
+
}
|
|
107
124
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
const releaseIcon = config.release.media?.find(
|
|
126
|
+
(asset: any) => asset.purpose === "icon"
|
|
127
|
+
)?.uri;
|
|
111
128
|
|
|
112
|
-
|
|
129
|
+
if (releaseIcon) {
|
|
130
|
+
const iconPath = path.join(process.cwd(), releaseIcon);
|
|
131
|
+
await checkIconCompatibility(iconPath, "Release");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!appIcon && !releaseIcon) {
|
|
135
|
+
throw new Error("Please specify at least one media entry of type icon in your configuration file");
|
|
113
136
|
}
|
|
114
137
|
|
|
115
138
|
config.release.media.forEach((item: CLIConfig["release"]["media"][0]) => {
|
|
@@ -119,9 +142,28 @@ export const getConfigFile = async (
|
|
|
119
142
|
}
|
|
120
143
|
});
|
|
121
144
|
|
|
145
|
+
const baselineSize = Object.keys(config.release.catalog["en-US"]).length;
|
|
146
|
+
Object.keys(config.release.catalog).forEach((locale) => {
|
|
147
|
+
const size = Object.keys(config.release.catalog[locale]).length;
|
|
148
|
+
|
|
149
|
+
if (size != baselineSize) {
|
|
150
|
+
throw new Error("Please ensure you have included all localized strings for all locales in your configuration file.");
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
122
154
|
return config;
|
|
123
155
|
};
|
|
124
156
|
|
|
157
|
+
const checkIconCompatibility = async (path: string, typeString: string) => {
|
|
158
|
+
if (!fs.existsSync(path) || !checkImageExtension(path)) {
|
|
159
|
+
throw new Error(`Please check the path to your ${typeString} icon and ensure the file is a jpeg, png, or webp file.`)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (await checkIconDimensions(path)) {
|
|
163
|
+
throw new Error("Icons must have square dimensions and be no greater than 512px by 512px.")
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
125
167
|
const checkImageExtension = (uri: string): boolean => {
|
|
126
168
|
const fileExt = path.extname(uri).toLowerCase();
|
|
127
169
|
return (
|
|
@@ -163,17 +205,27 @@ export const generateNetworkSuffix = (rpcUrl: string): string => {
|
|
|
163
205
|
export const showMessage = (
|
|
164
206
|
titleMessage = "",
|
|
165
207
|
contentMessage = "",
|
|
166
|
-
|
|
167
|
-
) => {
|
|
168
|
-
|
|
208
|
+
type: "standard" | "error" | "warning" = "standard",
|
|
209
|
+
): string => {
|
|
210
|
+
let color = "cyan";
|
|
211
|
+
if (type == "error") {
|
|
212
|
+
color = "redBright";
|
|
213
|
+
} else if (type == "warning") {
|
|
214
|
+
color = "yellow";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const msg = boxen(contentMessage, {
|
|
169
218
|
title: titleMessage,
|
|
170
219
|
padding: 1,
|
|
171
220
|
margin: 1,
|
|
172
221
|
borderStyle: 'single',
|
|
173
|
-
borderColor:
|
|
222
|
+
borderColor: color,
|
|
174
223
|
textAlignment: "left",
|
|
175
|
-
titleAlignment: "center"
|
|
176
|
-
})
|
|
224
|
+
titleAlignment: "center",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
console.log(msg);
|
|
228
|
+
return msg;
|
|
177
229
|
};
|
|
178
230
|
|
|
179
231
|
const checkIconDimensions = async (iconPath: string): Promise<boolean> => {
|
|
@@ -239,7 +291,7 @@ export const saveToConfig = async ({
|
|
|
239
291
|
app,
|
|
240
292
|
release,
|
|
241
293
|
}: SaveToConfigArgs) => {
|
|
242
|
-
const currentConfig = await
|
|
294
|
+
const currentConfig = await getConfigWithChecks();
|
|
243
295
|
|
|
244
296
|
delete currentConfig.publisher.icon;
|
|
245
297
|
delete currentConfig.app.icon;
|
|
@@ -262,7 +314,7 @@ export const saveToConfig = async ({
|
|
|
262
314
|
};
|
|
263
315
|
|
|
264
316
|
// TODO(jon): Verify the contents of the YAML file
|
|
265
|
-
fs.writeFileSync(`${process.cwd()}
|
|
317
|
+
fs.writeFileSync(`${process.cwd()}/${Constants.CONFIG_FILE_NAME}`, dump(newConfig));
|
|
266
318
|
};
|
|
267
319
|
|
|
268
320
|
export const getMetaplexInstance = (
|