From 932745f70f58524a90c2d80c08e24f44504cf468 Mon Sep 17 00:00:00 2001 From: konoart <91595347+konoart@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:53:12 +0200 Subject: [PATCH] Add initial alpha testing support (#289) * Add initial alpha testing support * Update schema for alpha testers and enforce max 10 limit. * Add alpha release warning. * Add undefined check. * Check if alpha app submitted without specifying any testers. * Update message * Dont save submission details for alpha app release. * Print requestId on developer submission window. --------- Co-authored-by: Ankur Jain --- example/config.yaml | 5 ++++ packages/cli/src/CliSetup.ts | 16 +++++++++++ packages/cli/src/CliUtils.ts | 9 +++++++ .../src/commands/publish/PublishCliSubmit.ts | 17 +++++++++--- .../src/commands/publish/PublishCliUpdate.ts | 17 +++++++++--- packages/cli/src/config/PublishDetails.ts | 16 +++++++++++ .../prebuild_schema/publishing_source.yaml | 7 ++++- .../core/src/publish/PublishCoreSubmit.ts | 27 +++++++++++++++++-- .../core/src/publish/PublishCoreUpdate.ts | 27 +++++++++++++++++-- .../core/src/publish/dapp_publisher_portal.ts | 20 +++++++++++++- packages/core/src/types.ts | 6 +++++ 11 files changed, 153 insertions(+), 14 deletions(-) diff --git a/example/config.yaml b/example/config.yaml index ac1345f..8018785 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -52,3 +52,8 @@ release: solana_mobile_dapp_publisher_portal: google_store_package: com.solanamobile.cutekitten.gps testing_instructions: Here are some steps informing Solana Mobile of how to test this dapp. You can specify multiple lines of instructions. For example, if a login is needed, you would add those details here. + alpha_testers: + - address: 3tgkMfug2gs82sy2wexQjMkR12JzFcX9rSLd9yM9m38g + comment: Employe#1 genesis token + - address: G65S4B3RkFpPAt9CwY4cZXptNSkTS6c8hFW1m89GCuB1 + comment: Employe#2 genesis token \ No newline at end of file diff --git a/packages/cli/src/CliSetup.ts b/packages/cli/src/CliSetup.ts index 7080923..d7c9ca6 100644 --- a/packages/cli/src/CliSetup.ts +++ b/packages/cli/src/CliSetup.ts @@ -11,6 +11,7 @@ import { checkForSelfUpdate, checkSubmissionNetwork, Constants, + alphaAppSubmissionMessage, dryRunSuccessMessage, generateNetworkSuffix, parseKeypair, @@ -303,6 +304,7 @@ publishCommand "-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal." ) + .option("-l, --alpha", "Flag to mark the submission as alpha test.") .action( async ({ appMintAddress, @@ -312,6 +314,7 @@ publishCommand compliesWithSolanaDappStorePolicies, requestorIsAuthorized, dryRun, + alpha, }) => { await tryWithErrorMessage(async () => { await checkForSelfUpdate(); @@ -323,6 +326,10 @@ publishCommand throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.") } + if (alpha) { + alphaAppSubmissionMessage() + } + const signer = parseKeypair(keypair); if (signer) { if (config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null) { @@ -335,6 +342,7 @@ publishCommand compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies, requestorIsAuthorized: requestorIsAuthorized, critical: false, + alphaTest: alpha, }); } else { await publishSubmitCommand({ @@ -345,6 +353,7 @@ publishCommand dryRun: dryRun, compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies, requestorIsAuthorized: requestorIsAuthorized, + alphaTest: alpha, }); } @@ -389,6 +398,7 @@ publishCommand "-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal." ) + .option("-l, --alpha", "Flag to mark the submission as alpha test.") .action( async ({ appMintAddress, @@ -399,6 +409,7 @@ publishCommand requestorIsAuthorized, critical, dryRun, + alpha, }) => { await tryWithErrorMessage(async () => { await checkForSelfUpdate(); @@ -410,6 +421,10 @@ publishCommand throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.") } + if (alpha) { + alphaAppSubmissionMessage() + } + const signer = parseKeypair(keypair); if (signer) { await publishUpdateCommand({ @@ -421,6 +436,7 @@ publishCommand compliesWithSolanaDappStorePolicies, requestorIsAuthorized, critical, + alphaTest: alpha, }); if (dryRun) { diff --git a/packages/cli/src/CliUtils.ts b/packages/cli/src/CliUtils.ts index 3ea4140..68341b7 100644 --- a/packages/cli/src/CliUtils.ts +++ b/packages/cli/src/CliUtils.ts @@ -143,6 +143,15 @@ export const dryRunSuccessMessage = () => { showMessage("Dry run", "Dry run was successful", "standard") } +export const alphaAppSubmissionMessage = () => { + showMessage( + "Alpha release", + "Alpha releases are not reviewed on dApp store and are meant for internal testing only.\n" + + "Run the `npx dapp-store publish submit ...` command again without the `--alpha` param to publish the app", + "warning" + ) +} + export const showNetworkWarningIfApplicable = (rpcUrl: string) => { if (isDevnet(rpcUrl)) { showMessage("Devnet Mode", "Running on Devnet", "warning") diff --git a/packages/cli/src/commands/publish/PublishCliSubmit.ts b/packages/cli/src/commands/publish/PublishCliSubmit.ts index f2d7eb3..2b30b40 100644 --- a/packages/cli/src/commands/publish/PublishCliSubmit.ts +++ b/packages/cli/src/commands/publish/PublishCliSubmit.ts @@ -14,6 +14,7 @@ type PublishSubmitCommandInput = { dryRun: boolean; compliesWithSolanaDappStorePolicies: boolean; requestorIsAuthorized: boolean; + alphaTest?: boolean; }; export const publishSubmitCommand = async ({ @@ -24,6 +25,7 @@ export const publishSubmitCommand = async ({ dryRun = false, compliesWithSolanaDappStorePolicies = false, requestorIsAuthorized = false, + alphaTest }: PublishSubmitCommandInput) => { showMessage( `Publishing Estimates`, @@ -52,6 +54,10 @@ export const publishSubmitCommand = async ({ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore, } = await loadPublishDetailsWithChecks(); + if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) { + throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`) + } + const sign = ((buf: Buffer) => nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair; @@ -74,12 +80,15 @@ export const publishSubmitCommand = async ({ solanaMobileDappPublisherPortalDetails, compliesWithSolanaDappStorePolicies, requestorIsAuthorized, + alphaTest, }, dryRun ); - await writeToPublishDetails( - { - lastUpdatedVersionOnStore: { address: releaseAddr } - }); + if (!alphaTest) { + await writeToPublishDetails( + { + lastUpdatedVersionOnStore: { address: releaseAddr } + }); + } }; diff --git a/packages/cli/src/commands/publish/PublishCliUpdate.ts b/packages/cli/src/commands/publish/PublishCliUpdate.ts index d70d619..533ec7e 100644 --- a/packages/cli/src/commands/publish/PublishCliUpdate.ts +++ b/packages/cli/src/commands/publish/PublishCliUpdate.ts @@ -14,6 +14,7 @@ type PublishUpdateCommandInput = { compliesWithSolanaDappStorePolicies: boolean; requestorIsAuthorized: boolean; critical: boolean; + alphaTest?: boolean; }; export const publishUpdateCommand = async ({ @@ -25,6 +26,7 @@ export const publishUpdateCommand = async ({ compliesWithSolanaDappStorePolicies = false, requestorIsAuthorized = false, critical = false, + alphaTest, }: PublishUpdateCommandInput) => { showMessage( @@ -60,6 +62,10 @@ export const publishUpdateCommand = async ({ lastUpdatedVersionOnStore: lastUpdatedVersionOnStore } = await loadPublishDetailsWithChecks(); + if (alphaTest && solanaMobileDappPublisherPortalDetails.alpha_testers == undefined) { + throw new Error(`Alpha test submission without specifying any testers.\nAdd field alpha_testers in your 'config.yaml' file.`) + } + const sign = ((buf: Buffer) => nacl.sign(buf, signer.secretKey)) as SignWithPublisherKeypair; @@ -83,11 +89,14 @@ export const publishUpdateCommand = async ({ compliesWithSolanaDappStorePolicies, requestorIsAuthorized, criticalUpdate: critical, + alphaTest, }, dryRun ); - await writeToPublishDetails( - { - lastUpdatedVersionOnStore: { address: releaseAddr } - }); + if (!alphaTest) { + await writeToPublishDetails( + { + lastUpdatedVersionOnStore: { address: releaseAddr } + }); + } }; diff --git a/packages/cli/src/config/PublishDetails.ts b/packages/cli/src/config/PublishDetails.ts index 743f053..7507fc9 100644 --- a/packages/cli/src/config/PublishDetails.ts +++ b/packages/cli/src/config/PublishDetails.ts @@ -20,6 +20,7 @@ import util from "util"; import { imageSize } from "image-size"; import { exec } from "child_process"; import getVideoDimensions from "get-video-dimensions"; +import { PublicKey } from "@solana/web3.js"; const runImgSize = util.promisify(imageSize); const runExec = util.promisify(exec); @@ -179,6 +180,21 @@ export const loadPublishDetailsWithChecks = async ( } } + const alpha_testers = config.solana_mobile_dapp_publisher_portal.alpha_testers; + if (alpha_testers !== undefined) { + for (const wallet of alpha_testers) { + try { + void new PublicKey(wallet.address); + } catch (e: unknown) { + throw new Error(`invalid alpha tester wallet address <${wallet}>`); + } + } + + if (alpha_testers.size > 10) { + throw new Error(`Alpha testers are limited to 10 per app submission`); + } + } + return config; }; diff --git a/packages/cli/src/prebuild_schema/publishing_source.yaml b/packages/cli/src/prebuild_schema/publishing_source.yaml index 794562b..ac69ffc 100644 --- a/packages/cli/src/prebuild_schema/publishing_source.yaml +++ b/packages/cli/src/prebuild_schema/publishing_source.yaml @@ -51,4 +51,9 @@ release: solana_mobile_dapp_publisher_portal: google_store_package: <> testing_instructions: >- - <> \ No newline at end of file + <> + alpha_testers: + - address: <> + comment: <> + - address: <> + comment: <> \ No newline at end of file diff --git a/packages/core/src/publish/PublishCoreSubmit.ts b/packages/core/src/publish/PublishCoreSubmit.ts index 76bcdb8..754a5d2 100644 --- a/packages/core/src/publish/PublishCoreSubmit.ts +++ b/packages/core/src/publish/PublishCoreSubmit.ts @@ -8,6 +8,8 @@ import { CONTACT_PROPERTY_WEBSITE, submitRequestToSolanaDappPublisherPortal, TICKET_OBJECT_ID, + TICKET_PROPERTY_ALPHA_TEST, + TICKET_PROPERTY_ALPHA_TESTERS, TICKET_PROPERTY_ATTESTATION_PAYLOAD, TICKET_PROPERTY_AUTHORIZED_REQUEST, TICKET_PROPERTY_DAPP_COLLECTION_ACCOUNT_ADDRESS, @@ -28,7 +30,8 @@ const createSubmitRequest = async ( publisherDetails: Publisher, solanaMobileDappPublisherPortalDetails: SolanaMobileDappPublisherPortal, compliesWithSolanaDappStorePolicies: boolean, - requestorIsAuthorized: boolean + requestorIsAuthorized: boolean, + alphaTest?: boolean ) => { const { attestationPayload, requestUniqueId } = await createAttestationPayload(connection, sign); @@ -90,6 +93,14 @@ const createSubmitRequest = async ( }); } + if (alphaTest) { + request.fields.push({ + objectTypeId: TICKET_OBJECT_ID, + name: TICKET_PROPERTY_ALPHA_TEST, + value: true, + }); + } + if (solanaMobileDappPublisherPortalDetails.testing_instructions !== undefined) { request.fields.push( { @@ -100,6 +111,14 @@ const createSubmitRequest = async ( ); } + if (solanaMobileDappPublisherPortalDetails.alpha_testers !== undefined && solanaMobileDappPublisherPortalDetails.alpha_testers.length > 0) { + request.fields.push({ + objectTypeId: TICKET_OBJECT_ID, + name: TICKET_PROPERTY_ALPHA_TESTERS, + value: solanaMobileDappPublisherPortalDetails.alpha_testers.map(tester => tester.address).toString(), + }); + } + return request; }; @@ -110,6 +129,7 @@ export type PublishSubmitInput = { solanaMobileDappPublisherPortalDetails: SolanaMobileDappPublisherPortal; compliesWithSolanaDappStorePolicies: boolean; requestorIsAuthorized: boolean; + alphaTest?: boolean; }; export const publishSubmit = async ( @@ -121,6 +141,7 @@ export const publishSubmit = async ( solanaMobileDappPublisherPortalDetails, compliesWithSolanaDappStorePolicies, requestorIsAuthorized, + alphaTest, } : PublishSubmitInput, dryRun: boolean, ) => { @@ -132,7 +153,9 @@ export const publishSubmit = async ( publisherDetails, solanaMobileDappPublisherPortalDetails, compliesWithSolanaDappStorePolicies, - requestorIsAuthorized); + requestorIsAuthorized, + alphaTest + ); return submitRequestToSolanaDappPublisherPortal(submitRequest, URL_FORM_SUBMIT, dryRun); }; diff --git a/packages/core/src/publish/PublishCoreUpdate.ts b/packages/core/src/publish/PublishCoreUpdate.ts index fe1713d..c1b2a72 100644 --- a/packages/core/src/publish/PublishCoreUpdate.ts +++ b/packages/core/src/publish/PublishCoreUpdate.ts @@ -8,6 +8,8 @@ import { CONTACT_PROPERTY_WEBSITE, submitRequestToSolanaDappPublisherPortal, TICKET_OBJECT_ID, + TICKET_PROPERTY_ALPHA_TEST, + TICKET_PROPERTY_ALPHA_TESTERS, TICKET_PROPERTY_ATTESTATION_PAYLOAD, TICKET_PROPERTY_AUTHORIZED_REQUEST, TICKET_PROPERTY_CRITICAL_UPDATE, @@ -29,7 +31,8 @@ const createUpdateRequest = async ( solanaMobileDappPublisherPortalDetails: SolanaMobileDappPublisherPortal, compliesWithSolanaDappStorePolicies: boolean, requestorIsAuthorized: boolean, - criticalUpdate: boolean + criticalUpdate: boolean, + alphaTest?: boolean ) => { const { attestationPayload, requestUniqueId } = await createAttestationPayload(connection, sign); @@ -93,6 +96,14 @@ const createUpdateRequest = async ( ); } + if (alphaTest) { + request.fields.push({ + objectTypeId: TICKET_OBJECT_ID, + name: TICKET_PROPERTY_ALPHA_TEST, + value: true, + }); + } + if (solanaMobileDappPublisherPortalDetails.testing_instructions !== undefined) { request.fields.push( { @@ -103,6 +114,14 @@ const createUpdateRequest = async ( ); } + if (solanaMobileDappPublisherPortalDetails.alpha_testers !== undefined && solanaMobileDappPublisherPortalDetails.alpha_testers.length > 0) { + request.fields.push({ + objectTypeId: TICKET_OBJECT_ID, + name: TICKET_PROPERTY_ALPHA_TESTERS, + value: solanaMobileDappPublisherPortalDetails.alpha_testers.map(tester => tester.address).toString(), + }); + } + return request; }; @@ -114,6 +133,7 @@ export type PublishUpdateInput = { compliesWithSolanaDappStorePolicies: boolean; requestorIsAuthorized: boolean; criticalUpdate: boolean; + alphaTest?: boolean; }; export const publishUpdate = async ( @@ -126,6 +146,7 @@ export const publishUpdate = async ( compliesWithSolanaDappStorePolicies, requestorIsAuthorized, criticalUpdate, + alphaTest, } : PublishUpdateInput, dryRun: boolean, ) => { @@ -138,7 +159,9 @@ export const publishUpdate = async ( solanaMobileDappPublisherPortalDetails, compliesWithSolanaDappStorePolicies, requestorIsAuthorized, - criticalUpdate); + criticalUpdate, + alphaTest + ); return submitRequestToSolanaDappPublisherPortal(updateRequest, URL_FORM_UPDATE, dryRun); }; diff --git a/packages/core/src/publish/dapp_publisher_portal.ts b/packages/core/src/publish/dapp_publisher_portal.ts index 83b4ac5..2a24ead 100644 --- a/packages/core/src/publish/dapp_publisher_portal.ts +++ b/packages/core/src/publish/dapp_publisher_portal.ts @@ -18,6 +18,8 @@ export const TICKET_PROPERTY_GOOGLE_PLAY_STORE_PACKAGE_NAME = "google_play_store export const TICKET_PROPERTY_POLICY_COMPLIANT = "complies_with_solana_dapp_store_policies"; // boolean export const TICKET_PROPERTY_REQUEST_UNIQUE_ID = "request_unique_id"; // string (32 base-10 digits) export const TICKET_PROPERTY_TESTING_INSTRUCTIONS = "testing_instructions"; // string +export const TICKET_PROPERTY_ALPHA_TEST = "alpha_test"; // boolean +export const TICKET_PROPERTY_ALPHA_TESTERS = "alpha_testers"; // string export const FORM_SUBMIT = "1464247f-6804-46e1-8114-952f372daa81"; export const FORM_UPDATE = "87b4cbe7-957f-495c-a132-8b789678883d"; @@ -46,7 +48,23 @@ export const submitRequestToSolanaDappPublisherPortal = async ( if (!dryRun) { await axios(config) .then((response) => { - console.info(`dApp publisher portal response:`, response.data); + const isAlphaObject = request.fields.find((obj: { objectTypeId: string, name: string; value: string}) => { + return obj.name === TICKET_PROPERTY_ALPHA_TEST + }) + + if (isAlphaObject !== undefined && isAlphaObject.value) { + const requestUniqueId = request.fields.find((obj: { objectTypeId: string, name: string; value: string}) => { + return obj.name === TICKET_PROPERTY_REQUEST_UNIQUE_ID + }).value + console.log( + `Your alpha submission has been received.\n` + + `It will not be reviewed or published to users.\n` + + `Use nonce '${requestUniqueId}' to launch alpha app.\n` + + `This can only be used on devices for which the genesis token was listed in your 'config.yaml'` + ) + } else { + console.info(`dApp publisher portal response:`, response.data); + } }) .catch((error) => { if (error.response) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 914b742..febc25e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -81,9 +81,15 @@ export type Release = { }; }; +export type AlphaTester = { + address: string; + comment: string; +} + export type SolanaMobileDappPublisherPortal = { google_store_package: string; testing_instructions: string; + alpha_testers: AlphaTester[]; }; export type LastSubmittedVersionOnChain = {