@solana-mobile/dapp-store-cli 0.1.1-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.
Files changed (86) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +142 -0
  3. package/bin/dapp-store.js +3 -0
  4. package/lib/esm/commands/create/app.js +37 -0
  5. package/lib/esm/commands/create/app.js.map +1 -0
  6. package/lib/esm/commands/create/index.js +44 -0
  7. package/lib/esm/commands/create/index.js.map +1 -0
  8. package/lib/esm/commands/create/publisher.js +33 -0
  9. package/lib/esm/commands/create/publisher.js.map +1 -0
  10. package/lib/esm/commands/create/release.js +48 -0
  11. package/lib/esm/commands/create/release.js.map +1 -0
  12. package/lib/esm/commands/index.js +4 -0
  13. package/lib/esm/commands/index.js.map +1 -0
  14. package/lib/esm/commands/publish/index.js +25 -0
  15. package/lib/esm/commands/publish/index.js.map +1 -0
  16. package/lib/esm/commands/publish/remove.js +20 -0
  17. package/lib/esm/commands/publish/remove.js.map +1 -0
  18. package/lib/esm/commands/publish/submit.js +25 -0
  19. package/lib/esm/commands/publish/submit.js.map +1 -0
  20. package/lib/esm/commands/publish/support.js +20 -0
  21. package/lib/esm/commands/publish/support.js.map +1 -0
  22. package/lib/esm/commands/publish/update.js +26 -0
  23. package/lib/esm/commands/publish/update.js.map +1 -0
  24. package/lib/esm/commands/validate.js +40 -0
  25. package/lib/esm/commands/validate.js.map +1 -0
  26. package/lib/esm/config/index.js +19 -0
  27. package/lib/esm/config/index.js.map +1 -0
  28. package/lib/esm/config/schema.json +228 -0
  29. package/lib/esm/index.js +259 -0
  30. package/lib/esm/index.js.map +1 -0
  31. package/lib/esm/upload/CachedStorageDriver.js +64 -0
  32. package/lib/esm/upload/CachedStorageDriver.js.map +1 -0
  33. package/lib/esm/upload/index.js +2 -0
  34. package/lib/esm/upload/index.js.map +1 -0
  35. package/lib/esm/utils.js +171 -0
  36. package/lib/esm/utils.js.map +1 -0
  37. package/lib/types/commands/create/app.d.ts +12 -0
  38. package/lib/types/commands/create/app.d.ts.map +1 -0
  39. package/lib/types/commands/create/index.d.ts +4 -0
  40. package/lib/types/commands/create/index.d.ts.map +1 -0
  41. package/lib/types/commands/create/publisher.d.ts +9 -0
  42. package/lib/types/commands/create/publisher.d.ts.map +1 -0
  43. package/lib/types/commands/create/release.d.ts +14 -0
  44. package/lib/types/commands/create/release.d.ts.map +1 -0
  45. package/lib/types/commands/index.d.ts +4 -0
  46. package/lib/types/commands/index.d.ts.map +1 -0
  47. package/lib/types/commands/publish/index.d.ts +5 -0
  48. package/lib/types/commands/publish/index.d.ts.map +1 -0
  49. package/lib/types/commands/publish/remove.d.ts +12 -0
  50. package/lib/types/commands/publish/remove.d.ts.map +1 -0
  51. package/lib/types/commands/publish/submit.d.ts +12 -0
  52. package/lib/types/commands/publish/submit.d.ts.map +1 -0
  53. package/lib/types/commands/publish/support.d.ts +12 -0
  54. package/lib/types/commands/publish/support.d.ts.map +1 -0
  55. package/lib/types/commands/publish/update.d.ts +13 -0
  56. package/lib/types/commands/publish/update.d.ts.map +1 -0
  57. package/lib/types/commands/validate.d.ts +6 -0
  58. package/lib/types/commands/validate.d.ts.map +1 -0
  59. package/lib/types/config/index.d.ts +10 -0
  60. package/lib/types/config/index.d.ts.map +1 -0
  61. package/lib/types/index.d.ts +2 -0
  62. package/lib/types/index.d.ts.map +1 -0
  63. package/lib/types/upload/CachedStorageDriver.d.ts +30 -0
  64. package/lib/types/upload/CachedStorageDriver.d.ts.map +1 -0
  65. package/lib/types/upload/index.d.ts +2 -0
  66. package/lib/types/upload/index.d.ts.map +1 -0
  67. package/lib/types/utils.d.ts +21 -0
  68. package/lib/types/utils.d.ts.map +1 -0
  69. package/package.json +49 -0
  70. package/src/commands/create/app.ts +88 -0
  71. package/src/commands/create/index.ts +48 -0
  72. package/src/commands/create/publisher.ts +78 -0
  73. package/src/commands/create/release.ts +107 -0
  74. package/src/commands/index.ts +3 -0
  75. package/src/commands/publish/index.ts +29 -0
  76. package/src/commands/publish/remove.ts +46 -0
  77. package/src/commands/publish/submit.ts +55 -0
  78. package/src/commands/publish/support.ts +46 -0
  79. package/src/commands/publish/update.ts +58 -0
  80. package/src/commands/validate.ts +67 -0
  81. package/src/config/index.ts +40 -0
  82. package/src/config/schema.json +228 -0
  83. package/src/index.ts +435 -0
  84. package/src/upload/CachedStorageDriver.ts +104 -0
  85. package/src/upload/index.ts +1 -0
  86. package/src/utils.ts +275 -0
@@ -0,0 +1,88 @@
1
+ import type { App } from "@solana-mobile/dapp-store-publishing-tools";
2
+ import { createApp } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import {
4
+ Connection,
5
+ Keypair,
6
+ PublicKey,
7
+ sendAndConfirmTransaction,
8
+ } from "@solana/web3.js";
9
+
10
+ import {
11
+ getConfigFile,
12
+ getMetaplexInstance,
13
+ saveToConfig,
14
+ } from "../../utils.js";
15
+
16
+ const createAppNft = async (
17
+ {
18
+ appDetails,
19
+ connection,
20
+ publisherMintAddress,
21
+ publisher,
22
+ }: {
23
+ appDetails: App;
24
+ connection: Connection;
25
+ publisherMintAddress: string;
26
+ publisher: Keypair;
27
+ },
28
+ { dryRun }: { dryRun?: boolean }
29
+ ) => {
30
+ const mintAddress = Keypair.generate();
31
+ const metaplex = getMetaplexInstance(connection, publisher);
32
+ const txBuilder = await createApp(
33
+ {
34
+ publisherMintAddress: new PublicKey(publisherMintAddress),
35
+ mintAddress,
36
+ appDetails,
37
+ },
38
+ { metaplex, publisher }
39
+ );
40
+
41
+ const blockhash = await connection.getLatestBlockhash();
42
+ const tx = txBuilder.toTransaction(blockhash);
43
+ tx.sign(mintAddress, publisher);
44
+
45
+ if (!dryRun) {
46
+ const txSig = await sendAndConfirmTransaction(connection, tx, [
47
+ publisher,
48
+ mintAddress,
49
+ ]);
50
+ console.info({ txSig, mintAddress: mintAddress.publicKey.toBase58() });
51
+ }
52
+
53
+ return { appAddress: mintAddress.publicKey.toBase58() };
54
+ };
55
+
56
+ type CreateAppCommandInput = {
57
+ publisherMintAddress: string;
58
+ signer: Keypair;
59
+ url: string;
60
+ dryRun?: boolean;
61
+ };
62
+
63
+ export const createAppCommand = async ({
64
+ signer,
65
+ url,
66
+ dryRun,
67
+ publisherMintAddress,
68
+ }: CreateAppCommandInput) => {
69
+ const connection = new Connection(url);
70
+
71
+ const { app: appDetails, publisher: publisherDetails } =
72
+ await getConfigFile();
73
+
74
+ const { appAddress } = await createAppNft(
75
+ {
76
+ connection,
77
+ publisher: signer,
78
+ publisherMintAddress: publisherDetails.address ?? publisherMintAddress,
79
+ appDetails,
80
+ },
81
+ { dryRun }
82
+ );
83
+
84
+ // TODO(sdlaver): dry-run should not modify config
85
+ saveToConfig({ app: { address: appAddress } });
86
+
87
+ return { appAddress };
88
+ };
@@ -0,0 +1,48 @@
1
+ export * from "./publisher.js";
2
+ export * from "./app.js";
3
+ export * from "./release.js";
4
+
5
+ /*
6
+ * Module responsible for creating publishers, apps, and releases (in that order)
7
+ * Anything that is out-of-order will be prompted back into order
8
+ * And steps that happen more than once will do their best to remember as much information as possible.
9
+ * We will ask questions and do our best to answer anything that's already been configured, and prompt for anything that's not
10
+ */
11
+
12
+ // We'll never ask for private keys or seed phrases
13
+ // You can use a burner signer to publish; all NFTs require verification from the publisher signer
14
+ // You can use a multisig or Ledger for that purpose
15
+
16
+ // Publisher
17
+ // Public key attached to a publisher must also verify applications and releases
18
+ // Most information here can be be edited after the fact
19
+ // Only required fields are name, address, publisher website, and contact
20
+ // Optional fields are: description, image_url (need dimensions!)
21
+
22
+ // App
23
+ // Publisher creator key required
24
+ // Most information can be edited after the fact
25
+ // Required: name, description, `android_package`
26
+ // Optional: Any additional creator keys
27
+ // TODO(jon): Probably okay to capture more information here like:
28
+ // - `license_url`
29
+ // - `copyright_url`
30
+ // - `privacy_policy_url`
31
+ // Release
32
+ // Publisher creator key required
33
+ // Immutable; information cannot be edited after publishing.
34
+ // Change based on the review process must result in a new release
35
+ // Required:
36
+ // - version (automatically prompt with semver + 1)
37
+ // - release notes (description)
38
+ // - publisher creator key
39
+ // - path to the APK
40
+ // Optional:
41
+ // - Media related to the release
42
+ // - New permissions (prompted)
43
+ // - New languages (prompted)
44
+ // Handles uploads of all files, sha'ing them
45
+ // Handles i18n (later)
46
+
47
+ // We'll attempt to read as much as possible from a provided `.yml` file
48
+ // If there are provided folders that are well-structured, we'll opt to use that too.
@@ -0,0 +1,78 @@
1
+ import type { Publisher } from "@solana-mobile/dapp-store-publishing-tools";
2
+ import { createPublisher } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import {
4
+ Connection,
5
+ Keypair,
6
+ sendAndConfirmTransaction,
7
+ } from "@solana/web3.js";
8
+
9
+ import {
10
+ getConfigFile,
11
+ getMetaplexInstance,
12
+ saveToConfig,
13
+ } from "../../utils.js";
14
+
15
+ const createPublisherNft = async (
16
+ {
17
+ connection,
18
+ publisher,
19
+ publisherDetails,
20
+ }: {
21
+ connection: Connection;
22
+ publisher: Keypair;
23
+ publisherDetails: Publisher;
24
+ },
25
+ { dryRun }: { dryRun: boolean }
26
+ ) => {
27
+ const mintAddress = Keypair.generate();
28
+ const metaplex = getMetaplexInstance(connection, publisher);
29
+ console.info(
30
+ `Creating publisher at address: ${mintAddress.publicKey.toBase58()}`
31
+ );
32
+ const txBuilder = await createPublisher(
33
+ { mintAddress, publisherDetails },
34
+ { metaplex, publisher }
35
+ );
36
+
37
+ const blockhash = await connection.getLatestBlockhash();
38
+ const tx = txBuilder.toTransaction(blockhash);
39
+ tx.sign(mintAddress, publisher);
40
+
41
+ if (!dryRun) {
42
+ const txSig = await sendAndConfirmTransaction(connection, tx, [
43
+ publisher,
44
+ mintAddress,
45
+ ]);
46
+ console.info({ txSig, mintAddress: mintAddress.publicKey.toBase58() });
47
+ }
48
+
49
+ return { publisherAddress: mintAddress.publicKey.toBase58() };
50
+ };
51
+
52
+ export const createPublisherCommand = async ({
53
+ signer,
54
+ url,
55
+ dryRun,
56
+ }: {
57
+ signer: Keypair;
58
+ url: string;
59
+ dryRun: boolean;
60
+ }) => {
61
+ const connection = new Connection(url);
62
+
63
+ const { publisher: publisherDetails } = await getConfigFile();
64
+
65
+ const { publisherAddress } = await createPublisherNft(
66
+ {
67
+ connection,
68
+ publisher: signer,
69
+ publisherDetails,
70
+ },
71
+ { dryRun }
72
+ );
73
+
74
+ // TODO(sdlaver): dry-run should not modify config
75
+ saveToConfig({ publisher: { address: publisherAddress } });
76
+
77
+ return { publisherAddress };
78
+ };
@@ -0,0 +1,107 @@
1
+ import type {
2
+ App,
3
+ Publisher,
4
+ Release,
5
+ } from "@solana-mobile/dapp-store-publishing-tools";
6
+ import { createRelease } from "@solana-mobile/dapp-store-publishing-tools";
7
+ import {
8
+ Connection,
9
+ Keypair,
10
+ PublicKey,
11
+ sendAndConfirmTransaction,
12
+ } from "@solana/web3.js";
13
+ import { CachedStorageDriver } from "../../upload/CachedStorageDriver.js";
14
+
15
+ import {
16
+ getConfigFile,
17
+ getMetaplexInstance,
18
+ saveToConfig,
19
+ } from "../../utils.js";
20
+
21
+ type CreateReleaseCommandInput = {
22
+ appMintAddress: string;
23
+ version: string;
24
+ buildToolsPath: string;
25
+ signer: Keypair;
26
+ url: string;
27
+ dryRun?: boolean;
28
+ };
29
+
30
+ const createReleaseNft = async ({
31
+ appMintAddress,
32
+ releaseDetails,
33
+ appDetails,
34
+ publisherDetails,
35
+ connection,
36
+ publisher,
37
+ }: {
38
+ appMintAddress: string;
39
+ releaseDetails: Release;
40
+ appDetails: App;
41
+ publisherDetails: Publisher;
42
+ connection: Connection;
43
+ publisher: Keypair;
44
+ }) => {
45
+ const releaseMintAddress = Keypair.generate();
46
+
47
+ const metaplex = getMetaplexInstance(connection, publisher);
48
+
49
+ const { txBuilder } = await createRelease(
50
+ {
51
+ appMintAddress: new PublicKey(appMintAddress),
52
+ releaseMintAddress,
53
+ releaseDetails,
54
+ appDetails,
55
+ publisherDetails,
56
+ },
57
+ { metaplex, publisher }
58
+ );
59
+
60
+ const blockhash = await connection.getLatestBlockhash();
61
+ const tx = txBuilder.toTransaction(blockhash);
62
+ tx.sign(releaseMintAddress, publisher);
63
+
64
+ const txSig = await sendAndConfirmTransaction(connection, tx, [
65
+ publisher,
66
+ releaseMintAddress,
67
+ ]);
68
+ console.info({
69
+ txSig,
70
+ releaseMintAddress: releaseMintAddress.publicKey.toBase58(),
71
+ });
72
+
73
+ return { releaseAddress: releaseMintAddress.publicKey.toBase58() };
74
+ };
75
+
76
+ export const createReleaseCommand = async ({
77
+ appMintAddress,
78
+ version,
79
+ buildToolsPath,
80
+ signer,
81
+ url,
82
+ dryRun = false,
83
+ }: CreateReleaseCommandInput) => {
84
+ const connection = new Connection(url);
85
+
86
+ const { release, app, publisher } = await getConfigFile(buildToolsPath);
87
+
88
+ if (!dryRun) {
89
+ const { releaseAddress } = await createReleaseNft({
90
+ appMintAddress: app.address ?? appMintAddress,
91
+ connection,
92
+ publisher: signer,
93
+ releaseDetails: {
94
+ ...release,
95
+ version,
96
+ },
97
+ appDetails: app,
98
+ publisherDetails: publisher,
99
+ });
100
+
101
+ saveToConfig({
102
+ release: { address: releaseAddress, version },
103
+ });
104
+
105
+ return { releaseAddress };
106
+ }
107
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./create/index.js";
2
+ export * from "./publish/index.js";
3
+ export * from "./validate.js";
@@ -0,0 +1,29 @@
1
+ export * from "./remove.js";
2
+ export * from "./submit.js";
3
+ export * from "./support.js";
4
+ export * from "./update.js";
5
+
6
+ /*
7
+ * Module responsible for submitting requests to the Solana dApp Store publisher portal
8
+ * Anything that is out-of-order will be prompted back into order
9
+ * And steps that happen more than once will do their best to remember as much information as possible.
10
+ * We will ask questions and do our best to answer anything that's already been configured, and prompt for anything that's not
11
+ */
12
+
13
+ // We'll never ask for private keys or seed phrases
14
+ // You must use the same signer(s) when submitting requests to the publisher portal as was used to publish
15
+ // your app on-chain.
16
+
17
+ // The Solana Mobile dApp publisher portal supports 4 different requests: `submit`, `update`, `remove`, and `support`.
18
+ // Each request includes:
19
+ // - a 32-digit randomly generated unique identifier
20
+ // - an attestation payload, signed with the private key of the dApp collection update authority
21
+ // - the dApp release NFT address
22
+ // - contact and company information for the requestor
23
+ // - a self-attestation that the requestor is authorized to make this request
24
+ // - additional fields, specific to the request type in question
25
+
26
+ // We'll attempt to read as much as possible from a provided `.yml` file
27
+ // If there are provided folders that are well-structured, we'll opt to use that too.
28
+
29
+ // Requests and responses are logged to the console, to facilitate use in an automated CI/CD environment.
@@ -0,0 +1,46 @@
1
+ import { Connection, Keypair } from "@solana/web3.js";
2
+ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import { publishRemove } from "@solana-mobile/dapp-store-publishing-tools";
4
+ import { getConfigFile } from "../../utils.js";
5
+ import nacl from "tweetnacl";
6
+
7
+ type PublishRemoveCommandInput = {
8
+ releaseMintAddress: string;
9
+ signer: Keypair;
10
+ url: string;
11
+ dryRun: boolean;
12
+ requestorIsAuthorized: boolean;
13
+ critical: boolean;
14
+ };
15
+
16
+ export const publishRemoveCommand = async ({
17
+ releaseMintAddress,
18
+ signer,
19
+ url,
20
+ dryRun = false,
21
+ requestorIsAuthorized = false,
22
+ critical = false,
23
+ }: PublishRemoveCommandInput) => {
24
+ if (!requestorIsAuthorized) {
25
+ console.error(
26
+ "ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
27
+ );
28
+ return;
29
+ }
30
+
31
+ const connection = new Connection(url);
32
+ const { publisher: publisherDetails } = await getConfigFile();
33
+ const sign = ((buf: Buffer) =>
34
+ nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
35
+
36
+ await publishRemove(
37
+ { connection, sign },
38
+ {
39
+ releaseMintAddress,
40
+ publisherDetails,
41
+ requestorIsAuthorized,
42
+ criticalUpdate: critical,
43
+ },
44
+ dryRun
45
+ );
46
+ };
@@ -0,0 +1,55 @@
1
+ import { Connection, Keypair } from "@solana/web3.js";
2
+ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import { publishSubmit } from "@solana-mobile/dapp-store-publishing-tools";
4
+ import nacl from "tweetnacl";
5
+ import { getConfigFile } from "../../utils.js";
6
+
7
+ type PublishSubmitCommandInput = {
8
+ releaseMintAddress: string;
9
+ signer: Keypair;
10
+ url: string;
11
+ dryRun: boolean;
12
+ compliesWithSolanaDappStorePolicies: boolean;
13
+ requestorIsAuthorized: boolean;
14
+ };
15
+
16
+ export const publishSubmitCommand = async ({
17
+ releaseMintAddress,
18
+ signer,
19
+ url,
20
+ dryRun = false,
21
+ compliesWithSolanaDappStorePolicies = false,
22
+ requestorIsAuthorized = false,
23
+ }: PublishSubmitCommandInput) => {
24
+ if (!compliesWithSolanaDappStorePolicies) {
25
+ console.error(
26
+ "ERROR: Cannot submit a request for which the requestor does not attest that it complies with Solana dApp Store policies"
27
+ );
28
+ return;
29
+ } else if (!requestorIsAuthorized) {
30
+ console.error(
31
+ "ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
32
+ );
33
+ return;
34
+ }
35
+
36
+ const connection = new Connection(url);
37
+ const {
38
+ publisher: publisherDetails,
39
+ solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
40
+ } = await getConfigFile();
41
+ const sign = ((buf: Buffer) =>
42
+ nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
43
+
44
+ await publishSubmit(
45
+ { connection, sign },
46
+ {
47
+ releaseMintAddress,
48
+ publisherDetails,
49
+ solanaMobileDappPublisherPortalDetails,
50
+ compliesWithSolanaDappStorePolicies,
51
+ requestorIsAuthorized,
52
+ },
53
+ dryRun
54
+ );
55
+ };
@@ -0,0 +1,46 @@
1
+ import { Connection, Keypair } from "@solana/web3.js";
2
+ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import { publishSupport } from "@solana-mobile/dapp-store-publishing-tools";
4
+ import { getConfigFile } from "../../utils.js";
5
+ import nacl from "tweetnacl";
6
+
7
+ type PublishSupportCommandInput = {
8
+ releaseMintAddress: string;
9
+ signer: Keypair;
10
+ url: string;
11
+ dryRun: boolean;
12
+ requestorIsAuthorized: boolean;
13
+ requestDetails: string;
14
+ };
15
+
16
+ export const publishSupportCommand = async ({
17
+ releaseMintAddress,
18
+ signer,
19
+ url,
20
+ dryRun = false,
21
+ requestorIsAuthorized = false,
22
+ requestDetails,
23
+ }: PublishSupportCommandInput) => {
24
+ if (!requestorIsAuthorized) {
25
+ console.error(
26
+ "ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
27
+ );
28
+ return;
29
+ }
30
+
31
+ const connection = new Connection(url);
32
+ const { publisher: publisherDetails } = await getConfigFile();
33
+ const sign = ((buf: Buffer) =>
34
+ nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
35
+
36
+ await publishSupport(
37
+ { connection, sign },
38
+ {
39
+ releaseMintAddress,
40
+ publisherDetails,
41
+ requestorIsAuthorized,
42
+ requestDetails,
43
+ },
44
+ dryRun
45
+ );
46
+ };
@@ -0,0 +1,58 @@
1
+ import { Connection, Keypair } from "@solana/web3.js";
2
+ import type { SignWithPublisherKeypair } from "@solana-mobile/dapp-store-publishing-tools";
3
+ import { publishUpdate } from "@solana-mobile/dapp-store-publishing-tools";
4
+ import { getConfigFile } from "../../utils.js";
5
+ import nacl from "tweetnacl";
6
+
7
+ type PublishUpdateCommandInput = {
8
+ releaseMintAddress: string;
9
+ signer: Keypair;
10
+ url: string;
11
+ dryRun: boolean;
12
+ compliesWithSolanaDappStorePolicies: boolean;
13
+ requestorIsAuthorized: boolean;
14
+ critical: boolean;
15
+ };
16
+
17
+ export const publishUpdateCommand = async ({
18
+ releaseMintAddress,
19
+ signer,
20
+ url,
21
+ dryRun = false,
22
+ compliesWithSolanaDappStorePolicies = false,
23
+ requestorIsAuthorized = false,
24
+ critical = false,
25
+ }: PublishUpdateCommandInput) => {
26
+ if (!compliesWithSolanaDappStorePolicies) {
27
+ console.error(
28
+ "ERROR: Cannot submit a request for which the requestor does not attest that it complies with Solana dApp Store policies"
29
+ );
30
+ return;
31
+ } else if (!requestorIsAuthorized) {
32
+ console.error(
33
+ "ERROR: Cannot submit a request for which the requestor does not attest they are authorized to do so"
34
+ );
35
+ return;
36
+ }
37
+
38
+ const connection = new Connection(url);
39
+ const {
40
+ publisher: publisherDetails,
41
+ solana_mobile_dapp_publisher_portal: solanaMobileDappPublisherPortalDetails,
42
+ } = await getConfigFile();
43
+ const sign = ((buf: Buffer) =>
44
+ nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair;
45
+
46
+ await publishUpdate(
47
+ { connection, sign },
48
+ {
49
+ releaseMintAddress,
50
+ publisherDetails,
51
+ solanaMobileDappPublisherPortalDetails,
52
+ compliesWithSolanaDappStorePolicies,
53
+ requestorIsAuthorized,
54
+ criticalUpdate: critical,
55
+ },
56
+ dryRun
57
+ );
58
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ createAppJson,
3
+ createPublisherJson,
4
+ createReleaseJson,
5
+ validateApp,
6
+ validatePublisher,
7
+ validateRelease,
8
+ } from "@solana-mobile/dapp-store-publishing-tools";
9
+ import { debug, getConfigFile } from "../utils.js";
10
+
11
+ import type { Keypair } from "@solana/web3.js";
12
+ import type { MetaplexFile } from "@metaplex-foundation/js";
13
+
14
+ export const validateCommand = async ({
15
+ signer,
16
+ buildToolsPath,
17
+ }: {
18
+ signer: Keypair;
19
+ buildToolsPath?: string;
20
+ }) => {
21
+ const {
22
+ publisher: publisherDetails,
23
+ app: appDetails,
24
+ release: releaseDetails,
25
+ } = await getConfigFile(buildToolsPath);
26
+
27
+ debug({ publisherDetails, appDetails, releaseDetails });
28
+
29
+ const publisherJson = createPublisherJson(publisherDetails);
30
+ if (typeof publisherJson.image !== "string") {
31
+ publisherJson.image = (publisherJson.image as MetaplexFile)?.fileName;
32
+ }
33
+ debug(JSON.stringify({ publisherJson }, null, 2));
34
+
35
+ try {
36
+ validatePublisher(publisherJson);
37
+ console.info(`Publisher JSON valid!`);
38
+ } catch (e) {
39
+ console.error(e);
40
+ }
41
+
42
+ const appJson = createAppJson(appDetails, signer.publicKey);
43
+ if (typeof appJson.image !== "string") {
44
+ appJson.image = (appJson.image as MetaplexFile)?.fileName;
45
+ }
46
+ debug(JSON.stringify({ appJson }, null, 2));
47
+
48
+ try {
49
+ validateApp(appJson);
50
+ console.info(`App JSON valid!`);
51
+ } catch (e) {
52
+ console.error(e);
53
+ }
54
+
55
+ const releaseJson = await createReleaseJson(
56
+ { releaseDetails, appDetails, publisherDetails },
57
+ signer.publicKey
58
+ );
59
+ debug(JSON.stringify({ releaseJson }, null, 2));
60
+
61
+ try {
62
+ validateRelease(releaseJson);
63
+ console.info(`Release JSON valid!`);
64
+ } catch (e) {
65
+ console.error(e);
66
+ }
67
+ };
@@ -0,0 +1,40 @@
1
+ import type {
2
+ App,
3
+ Publisher,
4
+ Release,
5
+ SolanaMobileDappPublisherPortal,
6
+ } from "@solana-mobile/dapp-store-publishing-tools";
7
+ import fs from "fs/promises";
8
+ import { load } from "js-yaml";
9
+
10
+ import Ajv from "ajv";
11
+
12
+ // eslint-disable-next-line require-extensions/require-extensions
13
+ import schemaJson from "./schema.json";
14
+
15
+ // TODO: Add version number return here
16
+ export interface CLIConfig {
17
+ publisher: Publisher;
18
+ app: App;
19
+ release: Release;
20
+ solana_mobile_dapp_publisher_portal: SolanaMobileDappPublisherPortal;
21
+ isValid: boolean;
22
+ }
23
+
24
+ const ajv = new Ajv({ strictTuples: false });
25
+ const validate = ajv.compile(schemaJson);
26
+
27
+ export const getConfig = async (configPath: string) => {
28
+ const configFile = await fs.readFile(configPath, "utf-8");
29
+ const configJson = load(configFile);
30
+
31
+ const valid = validate(load(configFile) as object);
32
+
33
+ if (!valid) {
34
+ console.error(validate.errors);
35
+ process.exit(1);
36
+ }
37
+
38
+ const config = load(configFile) as CLIConfig;
39
+ return config;
40
+ };