@solana-mobile/dapp-store-cli 0.4.1 → 0.4.3
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/bin/dapp-store.js +1 -1
- package/lib/CliSetup.js +730 -0
- package/lib/CliUtils.js +309 -0
- package/lib/__tests__/CliSetupTest.js +140 -0
- package/lib/commands/ValidateCommand.js +201 -0
- package/lib/commands/create/CreateCliApp.js +240 -0
- package/lib/commands/create/CreateCliPublisher.js +238 -0
- package/lib/commands/create/CreateCliRelease.js +270 -0
- package/lib/commands/create/index.js +41 -0
- package/lib/{esm/commands → commands}/index.js +0 -1
- package/lib/commands/publish/PublishCliRemove.js +186 -0
- package/lib/commands/publish/PublishCliSubmit.js +192 -0
- package/lib/commands/publish/PublishCliSupport.js +186 -0
- package/lib/commands/publish/PublishCliUpdate.js +193 -0
- package/lib/commands/publish/index.js +22 -0
- package/lib/{esm/commands → commands}/scaffolding/ScaffoldInit.js +7 -6
- package/lib/{esm/commands → commands}/scaffolding/index.js +0 -1
- package/lib/config/EnvVariables.js +59 -0
- package/lib/config/PublishDetails.js +555 -0
- package/lib/config/S3StorageManager.js +93 -0
- package/lib/config/index.js +2 -0
- package/lib/generated/config_obj.json +1 -0
- package/lib/generated/config_schema.json +1 -0
- package/lib/index.js +148 -0
- package/lib/package.json +74 -0
- package/lib/prebuild_schema/publishing_source.yaml +46 -0
- package/lib/prebuild_schema/schemagen.js +20 -0
- package/lib/upload/CachedStorageDriver.js +307 -0
- package/lib/{esm/upload → upload}/index.js +0 -1
- package/package.json +21 -7
- package/src/CliSetup.ts +512 -0
- package/src/CliUtils.ts +68 -19
- package/src/__tests__/CliSetupTest.ts +212 -0
- package/src/commands/create/CreateCliApp.ts +6 -1
- package/src/commands/create/CreateCliPublisher.ts +6 -1
- package/src/commands/create/CreateCliRelease.ts +6 -1
- package/src/config/EnvVariables.ts +39 -0
- package/src/config/PublishDetails.ts +4 -6
- package/src/config/S3StorageManager.ts +47 -0
- package/src/config/index.ts +2 -0
- package/src/index.ts +3 -510
- package/lib/esm/CliUtils.js +0 -105
- package/lib/esm/CliUtils.js.map +0 -1
- package/lib/esm/commands/ValidateCommand.js +0 -42
- package/lib/esm/commands/ValidateCommand.js.map +0 -1
- package/lib/esm/commands/create/CreateCliApp.js +0 -41
- package/lib/esm/commands/create/CreateCliApp.js.map +0 -1
- package/lib/esm/commands/create/CreateCliPublisher.js +0 -36
- package/lib/esm/commands/create/CreateCliPublisher.js.map +0 -1
- package/lib/esm/commands/create/CreateCliRelease.js +0 -50
- package/lib/esm/commands/create/CreateCliRelease.js.map +0 -1
- package/lib/esm/commands/create/index.js +0 -44
- package/lib/esm/commands/create/index.js.map +0 -1
- package/lib/esm/commands/index.js.map +0 -1
- package/lib/esm/commands/publish/PublishCliRemove.js +0 -26
- package/lib/esm/commands/publish/PublishCliRemove.js.map +0 -1
- package/lib/esm/commands/publish/PublishCliSubmit.js +0 -31
- package/lib/esm/commands/publish/PublishCliSubmit.js.map +0 -1
- package/lib/esm/commands/publish/PublishCliSupport.js +0 -26
- package/lib/esm/commands/publish/PublishCliSupport.js.map +0 -1
- package/lib/esm/commands/publish/PublishCliUpdate.js +0 -32
- package/lib/esm/commands/publish/PublishCliUpdate.js.map +0 -1
- package/lib/esm/commands/publish/index.js +0 -25
- package/lib/esm/commands/publish/index.js.map +0 -1
- package/lib/esm/commands/scaffolding/ScaffoldInit.js.map +0 -1
- package/lib/esm/commands/scaffolding/index.js.map +0 -1
- package/lib/esm/config/PublishDetails.js +0 -178
- package/lib/esm/config/PublishDetails.js.map +0 -1
- package/lib/esm/generated/config_obj.json +0 -1
- package/lib/esm/generated/config_schema.json +0 -1
- package/lib/esm/index.js +0 -307
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/package.json +0 -60
- package/lib/esm/upload/CachedStorageDriver.js +0 -66
- package/lib/esm/upload/CachedStorageDriver.js.map +0 -1
- package/lib/esm/upload/index.js.map +0 -1
- package/lib/types/CliUtils.d.ts +0 -20
- package/lib/types/CliUtils.d.ts.map +0 -1
- package/lib/types/commands/ValidateCommand.d.ts +0 -6
- package/lib/types/commands/ValidateCommand.d.ts.map +0 -1
- package/lib/types/commands/create/CreateCliApp.d.ts +0 -12
- package/lib/types/commands/create/CreateCliApp.d.ts.map +0 -1
- package/lib/types/commands/create/CreateCliPublisher.d.ts +0 -9
- package/lib/types/commands/create/CreateCliPublisher.d.ts.map +0 -1
- package/lib/types/commands/create/CreateCliRelease.d.ts +0 -13
- package/lib/types/commands/create/CreateCliRelease.d.ts.map +0 -1
- package/lib/types/commands/create/index.d.ts +0 -4
- package/lib/types/commands/create/index.d.ts.map +0 -1
- package/lib/types/commands/index.d.ts +0 -4
- package/lib/types/commands/index.d.ts.map +0 -1
- package/lib/types/commands/publish/PublishCliRemove.d.ts +0 -13
- package/lib/types/commands/publish/PublishCliRemove.d.ts.map +0 -1
- package/lib/types/commands/publish/PublishCliSubmit.d.ts +0 -13
- package/lib/types/commands/publish/PublishCliSubmit.d.ts.map +0 -1
- package/lib/types/commands/publish/PublishCliSupport.d.ts +0 -13
- package/lib/types/commands/publish/PublishCliSupport.d.ts.map +0 -1
- package/lib/types/commands/publish/PublishCliUpdate.d.ts +0 -14
- package/lib/types/commands/publish/PublishCliUpdate.d.ts.map +0 -1
- package/lib/types/commands/publish/index.d.ts +0 -5
- package/lib/types/commands/publish/index.d.ts.map +0 -1
- package/lib/types/commands/scaffolding/ScaffoldInit.d.ts +0 -2
- package/lib/types/commands/scaffolding/ScaffoldInit.d.ts.map +0 -1
- package/lib/types/commands/scaffolding/index.d.ts +0 -2
- package/lib/types/commands/scaffolding/index.d.ts.map +0 -1
- package/lib/types/config/PublishDetails.d.ts +0 -17
- package/lib/types/config/PublishDetails.d.ts.map +0 -1
- package/lib/types/index.d.ts +0 -2
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/upload/CachedStorageDriver.d.ts +0 -30
- package/lib/types/upload/CachedStorageDriver.d.ts.map +0 -1
- package/lib/types/upload/index.d.ts +0 -2
- package/lib/types/upload/index.d.ts.map +0 -1
package/src/CliSetup.ts
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { validateCommand } from "./commands/index.js";
|
|
3
|
+
import { createAppCommand, createPublisherCommand, createReleaseCommand } from "./commands/create/index.js";
|
|
4
|
+
import {
|
|
5
|
+
publishRemoveCommand,
|
|
6
|
+
publishSubmitCommand,
|
|
7
|
+
publishSupportCommand,
|
|
8
|
+
publishUpdateCommand
|
|
9
|
+
} from "./commands/publish/index.js";
|
|
10
|
+
import {
|
|
11
|
+
checkForSelfUpdate,
|
|
12
|
+
checkSubmissionNetwork,
|
|
13
|
+
Constants,
|
|
14
|
+
generateNetworkSuffix,
|
|
15
|
+
parseKeypair,
|
|
16
|
+
showMessage
|
|
17
|
+
} from "./CliUtils.js";
|
|
18
|
+
import * as dotenv from "dotenv";
|
|
19
|
+
import { initScaffold } from "./commands/scaffolding/index.js";
|
|
20
|
+
import { loadPublishDetails, loadPublishDetailsWithChecks } from "./config/PublishDetails.js";
|
|
21
|
+
|
|
22
|
+
dotenv.config();
|
|
23
|
+
|
|
24
|
+
const hasAddressInConfig = ({ address }: { address: string }) => {
|
|
25
|
+
return !!address;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const mainCli = new Command();
|
|
29
|
+
|
|
30
|
+
function resolveBuildToolsPath(buildToolsPath: string | undefined) {
|
|
31
|
+
// If a path was specified on the command line, use that
|
|
32
|
+
if (buildToolsPath !== undefined) {
|
|
33
|
+
return buildToolsPath;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// If a path is specified in a .env file, use that
|
|
37
|
+
if (process.env.ANDROID_TOOLS_DIR !== undefined) {
|
|
38
|
+
return process.env.ANDROID_TOOLS_DIR;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// No path was specified
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* This method should be updated with each new release of the CLI, and just do nothing when there isn't anything to report
|
|
47
|
+
*/
|
|
48
|
+
function latestReleaseMessage() {
|
|
49
|
+
showMessage(
|
|
50
|
+
`Publishing Tools Version ${ Constants.CLI_VERSION }`,
|
|
51
|
+
"- S3 bucket-based storage support added \n" +
|
|
52
|
+
"- short_description value reduced to 30 character limit",
|
|
53
|
+
"warning"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function tryWithErrorMessage(block: () => Promise<any>) {
|
|
58
|
+
try {
|
|
59
|
+
await block()
|
|
60
|
+
} catch (e) {
|
|
61
|
+
const errorMsg = (e as Error | null)?.message ?? "";
|
|
62
|
+
|
|
63
|
+
showMessage("Error", errorMsg, "error");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mainCli
|
|
68
|
+
.name("dapp-store")
|
|
69
|
+
.version(Constants.CLI_VERSION)
|
|
70
|
+
.description("CLI to assist with publishing to the Saga Dapp Store")
|
|
71
|
+
|
|
72
|
+
export const initCliCmd = mainCli
|
|
73
|
+
.command("init")
|
|
74
|
+
.description("First-time initialization of tooling configuration")
|
|
75
|
+
.action(async () => {
|
|
76
|
+
tryWithErrorMessage(async () => {
|
|
77
|
+
const msg = initScaffold();
|
|
78
|
+
|
|
79
|
+
showMessage("Initialized", msg);
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const createCliCmd = mainCli
|
|
84
|
+
.command("create")
|
|
85
|
+
.description("Create a `publisher`, `app`, or `release`")
|
|
86
|
+
|
|
87
|
+
export const createPublisherCliCmd = createCliCmd
|
|
88
|
+
.command("publisher")
|
|
89
|
+
.description("Create a publisher")
|
|
90
|
+
.requiredOption(
|
|
91
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
92
|
+
"Path to keypair file"
|
|
93
|
+
)
|
|
94
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
95
|
+
.option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
|
|
96
|
+
.option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
|
|
97
|
+
.action(async ({ keypair, url, dryRun, storageConfig }) => {
|
|
98
|
+
tryWithErrorMessage(async () => {
|
|
99
|
+
latestReleaseMessage();
|
|
100
|
+
await checkForSelfUpdate();
|
|
101
|
+
|
|
102
|
+
const signer = parseKeypair(keypair);
|
|
103
|
+
if (signer) {
|
|
104
|
+
const result: { publisherAddress: string } = await createPublisherCommand({ signer, url, dryRun, storageParams: storageConfig });
|
|
105
|
+
|
|
106
|
+
const displayUrl = `https://solscan.io/token/${result.publisherAddress}${generateNetworkSuffix(url)}`;
|
|
107
|
+
const resultText = `Publisher NFT successfully minted:\n${displayUrl}`;
|
|
108
|
+
|
|
109
|
+
showMessage("Success", resultText);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const createAppCliCmd = createCliCmd
|
|
115
|
+
.command("app")
|
|
116
|
+
.description("Create a app")
|
|
117
|
+
.requiredOption(
|
|
118
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
119
|
+
"Path to keypair file"
|
|
120
|
+
)
|
|
121
|
+
.option(
|
|
122
|
+
"-p, --publisher-mint-address <publisher-mint-address>",
|
|
123
|
+
"The mint address of the publisher NFT"
|
|
124
|
+
)
|
|
125
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
126
|
+
.option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
|
|
127
|
+
.option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
|
|
128
|
+
.action(async ({ publisherMintAddress, keypair, url, dryRun, storageConfig }) => {
|
|
129
|
+
tryWithErrorMessage(async () => {
|
|
130
|
+
latestReleaseMessage();
|
|
131
|
+
await checkForSelfUpdate();
|
|
132
|
+
|
|
133
|
+
const config = await loadPublishDetailsWithChecks();
|
|
134
|
+
|
|
135
|
+
if (!hasAddressInConfig(config.publisher) && !publisherMintAddress) {
|
|
136
|
+
throw new Error("Either specify a publisher mint address in the config file or specify as a CLI argument to this command.");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const signer = parseKeypair(keypair);
|
|
140
|
+
if (signer) {
|
|
141
|
+
const result = await createAppCommand({
|
|
142
|
+
publisherMintAddress: publisherMintAddress,
|
|
143
|
+
signer,
|
|
144
|
+
url,
|
|
145
|
+
dryRun,
|
|
146
|
+
storageParams: storageConfig
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const displayUrl = `https://solscan.io/token/${result.appAddress}${generateNetworkSuffix(url)}`;
|
|
150
|
+
const resultText = `App NFT successfully minted:\n${displayUrl}`;
|
|
151
|
+
|
|
152
|
+
showMessage("Success", resultText);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const createReleaseCliCmd = createCliCmd
|
|
158
|
+
.command("release")
|
|
159
|
+
.description("Create a release")
|
|
160
|
+
.requiredOption(
|
|
161
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
162
|
+
"Path to keypair file"
|
|
163
|
+
)
|
|
164
|
+
.option(
|
|
165
|
+
"-a, --app-mint-address <app-mint-address>",
|
|
166
|
+
"The mint address of the app NFT"
|
|
167
|
+
)
|
|
168
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
169
|
+
.option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT")
|
|
170
|
+
.option(
|
|
171
|
+
"-b, --build-tools-path <build-tools-path>",
|
|
172
|
+
"Path to Android build tools which contains AAPT2"
|
|
173
|
+
)
|
|
174
|
+
.option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details")
|
|
175
|
+
.action(async ({ appMintAddress, keypair, url, dryRun, buildToolsPath, storageConfig }) => {
|
|
176
|
+
tryWithErrorMessage(async () => {
|
|
177
|
+
latestReleaseMessage();
|
|
178
|
+
await checkForSelfUpdate();
|
|
179
|
+
|
|
180
|
+
const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
|
|
181
|
+
if (resolvedBuildToolsPath === undefined) {
|
|
182
|
+
throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const config = await loadPublishDetailsWithChecks();
|
|
186
|
+
if (!hasAddressInConfig(config.app) && !appMintAddress) {
|
|
187
|
+
throw new Error("Either specify an app mint address in the config file or specify as a CLI argument to this command")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const signer = parseKeypair(keypair);
|
|
191
|
+
if (signer) {
|
|
192
|
+
const result = await createReleaseCommand({
|
|
193
|
+
appMintAddress: appMintAddress,
|
|
194
|
+
buildToolsPath: resolvedBuildToolsPath,
|
|
195
|
+
signer,
|
|
196
|
+
url,
|
|
197
|
+
dryRun,
|
|
198
|
+
storageParams: storageConfig,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const displayUrl = `https://solscan.io/token/${result?.releaseAddress}${generateNetworkSuffix(url)}`;
|
|
202
|
+
const resultText = `Release NFT successfully minted:\n${displayUrl}`;
|
|
203
|
+
|
|
204
|
+
showMessage("Success", resultText);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
mainCli
|
|
211
|
+
.command("validate")
|
|
212
|
+
.description("Validates details prior to publishing")
|
|
213
|
+
.requiredOption(
|
|
214
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
215
|
+
"Path to keypair file"
|
|
216
|
+
)
|
|
217
|
+
.option(
|
|
218
|
+
"-b, --build-tools-path <build-tools-path>",
|
|
219
|
+
"Path to Android build tools which contains AAPT2"
|
|
220
|
+
)
|
|
221
|
+
.action(async ({ keypair, buildToolsPath }) => {
|
|
222
|
+
tryWithErrorMessage(async () => {
|
|
223
|
+
latestReleaseMessage();
|
|
224
|
+
await checkForSelfUpdate();
|
|
225
|
+
|
|
226
|
+
const resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
|
|
227
|
+
if (resolvedBuildToolsPath === undefined) {
|
|
228
|
+
throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.")
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const signer = parseKeypair(keypair);
|
|
232
|
+
if (signer) {
|
|
233
|
+
await validateCommand({
|
|
234
|
+
signer,
|
|
235
|
+
buildToolsPath: resolvedBuildToolsPath,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
//TODO: Add pretty formatting here, but will require more work than other sections
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const publishCommand = mainCli
|
|
244
|
+
.command("publish")
|
|
245
|
+
.description(
|
|
246
|
+
"Submit a publishing request (`submit`, `update`, `remove`, or `support`) to the Solana Mobile dApp publisher portal"
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
publishCommand
|
|
250
|
+
.command("submit")
|
|
251
|
+
.description("Submit a new app to the Solana Mobile dApp publisher portal")
|
|
252
|
+
.requiredOption(
|
|
253
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
254
|
+
"Path to keypair file"
|
|
255
|
+
)
|
|
256
|
+
.requiredOption(
|
|
257
|
+
"--complies-with-solana-dapp-store-policies",
|
|
258
|
+
"An attestation that the app complies with the Solana dApp Store policies"
|
|
259
|
+
)
|
|
260
|
+
.requiredOption(
|
|
261
|
+
"--requestor-is-authorized",
|
|
262
|
+
"An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
|
|
263
|
+
)
|
|
264
|
+
.option(
|
|
265
|
+
"-a, --app-mint-address <app-mint-address>",
|
|
266
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
267
|
+
)
|
|
268
|
+
.option(
|
|
269
|
+
"-r, --release-mint-address <release-mint-address>",
|
|
270
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
271
|
+
)
|
|
272
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
273
|
+
.option(
|
|
274
|
+
"-d, --dry-run",
|
|
275
|
+
"Flag for dry run. Doesn't submit the request to the publisher portal."
|
|
276
|
+
)
|
|
277
|
+
.action(
|
|
278
|
+
async ({
|
|
279
|
+
appMintAddress,
|
|
280
|
+
releaseMintAddress,
|
|
281
|
+
keypair,
|
|
282
|
+
url,
|
|
283
|
+
compliesWithSolanaDappStorePolicies,
|
|
284
|
+
requestorIsAuthorized,
|
|
285
|
+
dryRun,
|
|
286
|
+
}) => {
|
|
287
|
+
tryWithErrorMessage(async () => {
|
|
288
|
+
await checkForSelfUpdate();
|
|
289
|
+
await checkSubmissionNetwork(url);
|
|
290
|
+
|
|
291
|
+
const config = await loadPublishDetails(Constants.getConfigFilePath());
|
|
292
|
+
|
|
293
|
+
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
294
|
+
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const signer = parseKeypair(keypair);
|
|
298
|
+
if (signer) {
|
|
299
|
+
await publishSubmitCommand({
|
|
300
|
+
appMintAddress,
|
|
301
|
+
releaseMintAddress,
|
|
302
|
+
signer,
|
|
303
|
+
url,
|
|
304
|
+
dryRun,
|
|
305
|
+
compliesWithSolanaDappStorePolicies,
|
|
306
|
+
requestorIsAuthorized,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const resultText = "Successfully submitted to the Solana Mobile dApp publisher portal";
|
|
310
|
+
showMessage("Success", resultText);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
publishCommand
|
|
317
|
+
.command("update")
|
|
318
|
+
.description(
|
|
319
|
+
"Update an existing app on the Solana Mobile dApp publisher portal"
|
|
320
|
+
)
|
|
321
|
+
.requiredOption(
|
|
322
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
323
|
+
"Path to keypair file"
|
|
324
|
+
)
|
|
325
|
+
.requiredOption(
|
|
326
|
+
"--complies-with-solana-dapp-store-policies",
|
|
327
|
+
"An attestation that the app complies with the Solana dApp Store policies"
|
|
328
|
+
)
|
|
329
|
+
.requiredOption(
|
|
330
|
+
"--requestor-is-authorized",
|
|
331
|
+
"An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
|
|
332
|
+
)
|
|
333
|
+
.option(
|
|
334
|
+
"-a, --app-mint-address <app-mint-address>",
|
|
335
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
336
|
+
)
|
|
337
|
+
.option(
|
|
338
|
+
"-r, --release-mint-address <release-mint-address>",
|
|
339
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
340
|
+
)
|
|
341
|
+
.option("-c, --critical", "Flag for a critical app update request")
|
|
342
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
343
|
+
.option(
|
|
344
|
+
"-d, --dry-run",
|
|
345
|
+
"Flag for dry run. Doesn't submit the request to the publisher portal."
|
|
346
|
+
)
|
|
347
|
+
.action(
|
|
348
|
+
async ({
|
|
349
|
+
appMintAddress,
|
|
350
|
+
releaseMintAddress,
|
|
351
|
+
keypair,
|
|
352
|
+
url,
|
|
353
|
+
compliesWithSolanaDappStorePolicies,
|
|
354
|
+
requestorIsAuthorized,
|
|
355
|
+
critical,
|
|
356
|
+
dryRun,
|
|
357
|
+
}) => {
|
|
358
|
+
tryWithErrorMessage(async () => {
|
|
359
|
+
await checkForSelfUpdate();
|
|
360
|
+
await checkSubmissionNetwork(url);
|
|
361
|
+
|
|
362
|
+
const config = await loadPublishDetails(Constants.getConfigFilePath())
|
|
363
|
+
|
|
364
|
+
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
365
|
+
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const signer = parseKeypair(keypair);
|
|
369
|
+
if (signer) {
|
|
370
|
+
await publishUpdateCommand({
|
|
371
|
+
appMintAddress,
|
|
372
|
+
releaseMintAddress,
|
|
373
|
+
signer,
|
|
374
|
+
url,
|
|
375
|
+
dryRun,
|
|
376
|
+
compliesWithSolanaDappStorePolicies,
|
|
377
|
+
requestorIsAuthorized,
|
|
378
|
+
critical,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const resultText = "dApp successfully updated on the publisher portal";
|
|
382
|
+
showMessage("Success", resultText);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
publishCommand
|
|
389
|
+
.command("remove")
|
|
390
|
+
.description(
|
|
391
|
+
"Remove an existing app from the Solana Mobile dApp publisher portal"
|
|
392
|
+
)
|
|
393
|
+
.requiredOption(
|
|
394
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
395
|
+
"Path to keypair file"
|
|
396
|
+
)
|
|
397
|
+
.requiredOption(
|
|
398
|
+
"--requestor-is-authorized",
|
|
399
|
+
"An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
|
|
400
|
+
)
|
|
401
|
+
.option(
|
|
402
|
+
"-a, --app-mint-address <app-mint-address>",
|
|
403
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
404
|
+
)
|
|
405
|
+
.option(
|
|
406
|
+
"-r, --release-mint-address <release-mint-address>",
|
|
407
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
408
|
+
)
|
|
409
|
+
.option("-c, --critical", "Flag for a critical app removal request")
|
|
410
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
411
|
+
.option(
|
|
412
|
+
"-d, --dry-run",
|
|
413
|
+
"Flag for dry run. Doesn't submit the request to the publisher portal."
|
|
414
|
+
)
|
|
415
|
+
.action(
|
|
416
|
+
async ({
|
|
417
|
+
appMintAddress,
|
|
418
|
+
releaseMintAddress,
|
|
419
|
+
keypair,
|
|
420
|
+
url,
|
|
421
|
+
requestorIsAuthorized,
|
|
422
|
+
critical,
|
|
423
|
+
dryRun,
|
|
424
|
+
}) => {
|
|
425
|
+
tryWithErrorMessage(async () => {
|
|
426
|
+
await checkForSelfUpdate();
|
|
427
|
+
await checkSubmissionNetwork(url);
|
|
428
|
+
|
|
429
|
+
const config = await loadPublishDetails(Constants.getConfigFilePath())
|
|
430
|
+
|
|
431
|
+
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
432
|
+
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const signer = parseKeypair(keypair);
|
|
436
|
+
if (signer) {
|
|
437
|
+
await publishRemoveCommand({
|
|
438
|
+
appMintAddress,
|
|
439
|
+
releaseMintAddress,
|
|
440
|
+
signer,
|
|
441
|
+
url,
|
|
442
|
+
dryRun,
|
|
443
|
+
requestorIsAuthorized,
|
|
444
|
+
critical,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const resultText = "dApp successfully removed from the publisher portal";
|
|
448
|
+
showMessage("Success", resultText);
|
|
449
|
+
}
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
publishCommand
|
|
455
|
+
.command("support <request_details>")
|
|
456
|
+
.description(
|
|
457
|
+
"Submit a support request for an existing app on the Solana Mobile dApp publisher portal"
|
|
458
|
+
)
|
|
459
|
+
.requiredOption(
|
|
460
|
+
"-k, --keypair <path-to-keypair-file>",
|
|
461
|
+
"Path to keypair file"
|
|
462
|
+
)
|
|
463
|
+
.requiredOption(
|
|
464
|
+
"--requestor-is-authorized",
|
|
465
|
+
"An attestation that the party making this Solana dApp publisher portal request is authorized to do so"
|
|
466
|
+
)
|
|
467
|
+
.option(
|
|
468
|
+
"-a, --app-mint-address <app-mint-address>",
|
|
469
|
+
"The mint address of the app NFT. If not specified, the value from your config file will be used."
|
|
470
|
+
)
|
|
471
|
+
.option(
|
|
472
|
+
"-r, --release-mint-address <release-mint-address>",
|
|
473
|
+
"The mint address of the release NFT. If not specified, the value from your config file will be used."
|
|
474
|
+
)
|
|
475
|
+
.option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET)
|
|
476
|
+
.option(
|
|
477
|
+
"-d, --dry-run",
|
|
478
|
+
"Flag for dry run. Doesn't submit the request to the publisher portal."
|
|
479
|
+
)
|
|
480
|
+
.action(
|
|
481
|
+
async (
|
|
482
|
+
requestDetails,
|
|
483
|
+
{ appMintAddress, releaseMintAddress, keypair, url, requestorIsAuthorized, dryRun }
|
|
484
|
+
) => {
|
|
485
|
+
tryWithErrorMessage(async () => {
|
|
486
|
+
await checkForSelfUpdate();
|
|
487
|
+
await checkSubmissionNetwork(url);
|
|
488
|
+
|
|
489
|
+
const config = await loadPublishDetails(Constants.getConfigFilePath())
|
|
490
|
+
|
|
491
|
+
if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
|
|
492
|
+
throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.")
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const signer = parseKeypair(keypair);
|
|
496
|
+
if (signer) {
|
|
497
|
+
await publishSupportCommand({
|
|
498
|
+
appMintAddress,
|
|
499
|
+
releaseMintAddress,
|
|
500
|
+
signer,
|
|
501
|
+
url,
|
|
502
|
+
dryRun,
|
|
503
|
+
requestorIsAuthorized,
|
|
504
|
+
requestDetails,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const resultText = "Support request sent successfully";
|
|
508
|
+
showMessage("Success", resultText);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
);
|
package/src/CliUtils.ts
CHANGED
|
@@ -2,17 +2,29 @@ import fs from "fs";
|
|
|
2
2
|
import type { Connection } from "@solana/web3.js";
|
|
3
3
|
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
4
4
|
import debugModule from "debug";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BundlrStorageDriver,
|
|
7
|
+
keypairIdentity,
|
|
8
|
+
Metaplex,
|
|
9
|
+
} from "@metaplex-foundation/js";
|
|
6
10
|
import updateNotifier from "update-notifier";
|
|
7
11
|
import cliPackage from "./package.json" assert { type: "json" };
|
|
8
12
|
import boxen from "boxen";
|
|
9
13
|
import ver from "semver";
|
|
10
14
|
import { CachedStorageDriver } from "./upload/CachedStorageDriver.js";
|
|
15
|
+
import { EnvVariables } from "./config/index.js";
|
|
16
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
17
|
+
import { awsStorage } from "@metaplex-foundation/js-plugin-aws";
|
|
18
|
+
import { S3StorageManager } from "./config/index.js";
|
|
11
19
|
|
|
12
20
|
export class Constants {
|
|
13
|
-
static CLI_VERSION = "0.4.
|
|
21
|
+
static CLI_VERSION = "0.4.3";
|
|
14
22
|
static CONFIG_FILE_NAME = "config.yaml";
|
|
15
23
|
static DEFAULT_RPC_DEVNET = "https://api.devnet.solana.com";
|
|
24
|
+
|
|
25
|
+
static getConfigFilePath = () => {
|
|
26
|
+
return `${process.cwd()}/${Constants.CONFIG_FILE_NAME}`;
|
|
27
|
+
};
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
export const debug = debugModule("CLI");
|
|
@@ -24,21 +36,35 @@ export const checkForSelfUpdate = async () => {
|
|
|
24
36
|
const latestVer = new ver.SemVer(updateInfo.latest);
|
|
25
37
|
const currentVer = new ver.SemVer(updateInfo.current);
|
|
26
38
|
|
|
27
|
-
if (
|
|
28
|
-
|
|
39
|
+
if (
|
|
40
|
+
latestVer.major > currentVer.major ||
|
|
41
|
+
latestVer.minor > currentVer.minor
|
|
42
|
+
) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Please update to the latest version of the dApp Store CLI before proceeding."
|
|
45
|
+
);
|
|
29
46
|
}
|
|
30
47
|
};
|
|
31
48
|
|
|
32
|
-
export const checkMintedStatus = async (
|
|
49
|
+
export const checkMintedStatus = async (
|
|
50
|
+
conn: Connection,
|
|
51
|
+
pubAddr: string,
|
|
52
|
+
appAddr: string,
|
|
53
|
+
releaseAddr: string
|
|
54
|
+
) => {
|
|
33
55
|
const results = await conn.getMultipleAccountsInfo([
|
|
34
56
|
new PublicKey(pubAddr),
|
|
35
57
|
new PublicKey(appAddr),
|
|
36
58
|
new PublicKey(releaseAddr),
|
|
37
59
|
]);
|
|
38
60
|
|
|
39
|
-
const rentAccounts = results.filter(
|
|
61
|
+
const rentAccounts = results.filter(
|
|
62
|
+
(item) => !(item == undefined) && item?.lamports > 0
|
|
63
|
+
);
|
|
40
64
|
if (rentAccounts?.length != 3) {
|
|
41
|
-
throw new Error(
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Please ensure you have minted all of your NFTs before submitting to the Solana Mobile dApp publisher portal."
|
|
67
|
+
);
|
|
42
68
|
}
|
|
43
69
|
};
|
|
44
70
|
|
|
@@ -47,12 +73,12 @@ export const parseKeypair = (pathToKeypairFile: string) => {
|
|
|
47
73
|
const keypairFile = fs.readFileSync(pathToKeypairFile, "utf-8");
|
|
48
74
|
return Keypair.fromSecretKey(Buffer.from(JSON.parse(keypairFile)));
|
|
49
75
|
} catch (e) {
|
|
50
|
-
showMessage
|
|
51
|
-
(
|
|
76
|
+
showMessage(
|
|
52
77
|
"KeyPair Error",
|
|
53
|
-
"Something went wrong when attempting to retrieve the keypair at " +
|
|
78
|
+
"Something went wrong when attempting to retrieve the keypair at " +
|
|
79
|
+
pathToKeypairFile,
|
|
54
80
|
"error"
|
|
55
|
-
)
|
|
81
|
+
);
|
|
56
82
|
}
|
|
57
83
|
};
|
|
58
84
|
|
|
@@ -66,7 +92,9 @@ export const isTestnet = (rpcUrl: string): boolean => {
|
|
|
66
92
|
|
|
67
93
|
export const checkSubmissionNetwork = (rpcUrl: string) => {
|
|
68
94
|
if (isDevnet(rpcUrl) || isTestnet(rpcUrl)) {
|
|
69
|
-
throw new Error(
|
|
95
|
+
throw new Error(
|
|
96
|
+
"It looks like you are attempting to submit a request with a devnet or testnet RPC endpoint. Please ensure that your NFTs are minted on mainnet beta, and re-run with a mainnet beta RPC endpoint."
|
|
97
|
+
);
|
|
70
98
|
}
|
|
71
99
|
};
|
|
72
100
|
|
|
@@ -87,7 +115,7 @@ export const generateNetworkSuffix = (rpcUrl: string): string => {
|
|
|
87
115
|
export const showMessage = (
|
|
88
116
|
titleMessage = "",
|
|
89
117
|
contentMessage = "",
|
|
90
|
-
type: "standard" | "error" | "warning" = "standard"
|
|
118
|
+
type: "standard" | "error" | "warning" = "standard"
|
|
91
119
|
): string => {
|
|
92
120
|
let color = "cyan";
|
|
93
121
|
if (type == "error") {
|
|
@@ -100,7 +128,7 @@ export const showMessage = (
|
|
|
100
128
|
title: titleMessage,
|
|
101
129
|
padding: 1,
|
|
102
130
|
margin: 1,
|
|
103
|
-
borderStyle:
|
|
131
|
+
borderStyle: "single",
|
|
104
132
|
borderColor: color,
|
|
105
133
|
textAlignment: "left",
|
|
106
134
|
titleAlignment: "center",
|
|
@@ -112,24 +140,45 @@ export const showMessage = (
|
|
|
112
140
|
|
|
113
141
|
export const getMetaplexInstance = (
|
|
114
142
|
connection: Connection,
|
|
115
|
-
keypair: Keypair
|
|
143
|
+
keypair: Keypair,
|
|
144
|
+
storageParams: string = ""
|
|
116
145
|
) => {
|
|
117
146
|
const metaplex = Metaplex.make(connection).use(keypairIdentity(keypair));
|
|
118
147
|
const isDevnet = connection.rpcEndpoint.includes("devnet");
|
|
119
148
|
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
//TODO: Use DI for this
|
|
150
|
+
const s3Mgr = new S3StorageManager(new EnvVariables());
|
|
151
|
+
s3Mgr.parseCmdArg(storageParams);
|
|
152
|
+
|
|
153
|
+
if (s3Mgr.hasS3Config) {
|
|
154
|
+
const awsClient = new S3Client({
|
|
155
|
+
region: s3Mgr.s3Config.regionName,
|
|
156
|
+
credentials: {
|
|
157
|
+
accessKeyId: s3Mgr.s3Config.accessKey,
|
|
158
|
+
secretAccessKey: s3Mgr.s3Config.secretKey,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const bucketPlugin = awsStorage(awsClient, s3Mgr.s3Config.bucketName);
|
|
163
|
+
metaplex.use(bucketPlugin);
|
|
164
|
+
} else {
|
|
165
|
+
const bundlrStorageDriver = isDevnet
|
|
166
|
+
? new BundlrStorageDriver(metaplex, {
|
|
122
167
|
address: "https://devnet.bundlr.network",
|
|
123
168
|
providerUrl: Constants.DEFAULT_RPC_DEVNET,
|
|
124
169
|
})
|
|
125
|
-
|
|
170
|
+
: new BundlrStorageDriver(metaplex);
|
|
171
|
+
|
|
172
|
+
metaplex.storage().setDriver(bundlrStorageDriver);
|
|
173
|
+
}
|
|
126
174
|
|
|
127
175
|
metaplex.storage().setDriver(
|
|
128
|
-
new CachedStorageDriver(
|
|
176
|
+
new CachedStorageDriver(metaplex.storage().driver(), {
|
|
129
177
|
assetManifestPath: isDevnet
|
|
130
178
|
? "./.asset-manifest-devnet.json"
|
|
131
179
|
: "./.asset-manifest.json",
|
|
132
180
|
})
|
|
133
181
|
);
|
|
182
|
+
|
|
134
183
|
return metaplex;
|
|
135
184
|
};
|