diff --git a/example/config.yaml b/example/config.yaml index b2b7f95..ac1345f 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -5,44 +5,50 @@ publisher: email: hello@solanamobile.com media: - purpose: icon - uri: ./media/publisher_icon.jpeg + uri: ./media/publisher_icon.png app: name: Cute Kittens address: '' android_package: com.solanamobile.cutekittens urls: - license_url: https://cdn.org/license.html - copyright_url: https://cdn.org/copyright.html - privacy_policy_url: https://cdn.org/privacy.html + license_url: https://solanamobile.com/dapp-store-tos + copyright_url: https://solanamobile.com/dapp-store-tos + privacy_policy_url: https://solanamobile.com/privacy-policy website: https://solanamobile.com media: - purpose: icon - uri: ./media/app_icon.jpeg + uri: ./media/app_icon.png release: address: '' media: - purpose: icon uri: ./media/release_icon.png - purpose: screenshot - uri: ./media/app_screenshot.png + uri: ./media/screenshot_1.png + - purpose: video + uri: ./media/demo.mp4 - purpose: screenshot - uri: ./media/app_screenshot1.png + uri: ./media/screenshot_2.png - purpose: screenshot - uri: ./media/app_screenshot2.png + uri: ./media/screenshot_3.png - purpose: screenshot - uri: ./media/app_screenshot3.png - - purpose: screenshot - uri: ./media/app_screenshot4.png + uri: ./media/screenshot_4.png files: - purpose: install - uri: ./files/app-debug.apk + uri: ./files/app-release.apk catalog: en-US: - name: This is a single release - short_description: A short description + name: Cute Kitten + short_description: Stories about cat long_description: Some wonderful release notes, in long-form new_in_version: Something new in this version saga_features: Some information about saga specific features + fr-Fr: + name: App Name (in French) + short_description: App description (in French) + long_description: Some wonderful release notes, in long-form (in French) + new_in_version: Something new in this version (in French) + saga_features: Some information about saga specific features (in French) solana_mobile_dapp_publisher_portal: - google_store_package: com.company.dapp.otherpkg + 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. diff --git a/example/files/app-debug.apk b/example/files/app-debug.apk deleted file mode 100644 index 6f4c580..0000000 Binary files a/example/files/app-debug.apk and /dev/null differ diff --git a/example/files/app-release.apk b/example/files/app-release.apk new file mode 100644 index 0000000..7098443 Binary files /dev/null and b/example/files/app-release.apk differ diff --git a/example/media/app_icon.jpeg b/example/media/app_icon.jpeg deleted file mode 100644 index 4ac4072..0000000 Binary files a/example/media/app_icon.jpeg and /dev/null differ diff --git a/example/media/app_icon.png b/example/media/app_icon.png new file mode 100644 index 0000000..d82f159 Binary files /dev/null and b/example/media/app_icon.png differ diff --git a/example/media/app_screenshot1.png b/example/media/app_screenshot1.png deleted file mode 100644 index c9c2540..0000000 Binary files a/example/media/app_screenshot1.png and /dev/null differ diff --git a/example/media/app_screenshot2.png b/example/media/app_screenshot2.png deleted file mode 100644 index c9c2540..0000000 Binary files a/example/media/app_screenshot2.png and /dev/null differ diff --git a/example/media/app_screenshot3.png b/example/media/app_screenshot3.png deleted file mode 100644 index c9c2540..0000000 Binary files a/example/media/app_screenshot3.png and /dev/null differ diff --git a/example/media/app_screenshot4.png b/example/media/app_screenshot4.png deleted file mode 100644 index c9c2540..0000000 Binary files a/example/media/app_screenshot4.png and /dev/null differ diff --git a/example/media/demo.mp4 b/example/media/demo.mp4 new file mode 100644 index 0000000..8753484 Binary files /dev/null and b/example/media/demo.mp4 differ diff --git a/example/media/publisher_icon.jpeg b/example/media/publisher_icon.jpeg deleted file mode 100644 index 3388945..0000000 Binary files a/example/media/publisher_icon.jpeg and /dev/null differ diff --git a/example/media/publisher_icon.png b/example/media/publisher_icon.png new file mode 100644 index 0000000..9e90559 Binary files /dev/null and b/example/media/publisher_icon.png differ diff --git a/example/media/release_icon.png b/example/media/release_icon.png index 522219a..d82f159 100644 Binary files a/example/media/release_icon.png and b/example/media/release_icon.png differ diff --git a/example/media/screenshot_1.png b/example/media/screenshot_1.png new file mode 100644 index 0000000..4090e99 Binary files /dev/null and b/example/media/screenshot_1.png differ diff --git a/example/media/screenshot_2.png b/example/media/screenshot_2.png new file mode 100644 index 0000000..af85ca5 Binary files /dev/null and b/example/media/screenshot_2.png differ diff --git a/example/media/screenshot_3.png b/example/media/screenshot_3.png new file mode 100644 index 0000000..0b4b51f Binary files /dev/null and b/example/media/screenshot_3.png differ diff --git a/example/media/screenshot_4.png b/example/media/screenshot_4.png new file mode 100644 index 0000000..b68852a Binary files /dev/null and b/example/media/screenshot_4.png differ diff --git a/packages/cli/package.json b/packages/cli/package.json index 2599fa1..0ff8670 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,9 +34,9 @@ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" }, "devDependencies": { - "@metaplex-foundation/js": "0.20.0", "@jest/globals": "^29.5.0", "@jest/types": "^29.5.0", + "@metaplex-foundation/js": "0.20.0", "@swc/jest": "^0.2.26", "@types/commander": "^2.12.2", "@types/debug": "^4.1.7", @@ -64,6 +64,7 @@ "dotenv": "^16.0.3", "esm": "^3.2.25", "generate-schema": "^2.6.0", + "get-video-dimensions": "^1.0.0", "image-size": "^1.0.2", "js-yaml": "^4.1.0", "semver": "^7.3.8", diff --git a/packages/cli/src/commands/ValidateCommand.ts b/packages/cli/src/commands/ValidateCommand.ts index 3ffc67c..fc32bf9 100644 --- a/packages/cli/src/commands/ValidateCommand.ts +++ b/packages/cli/src/commands/ValidateCommand.ts @@ -36,7 +36,6 @@ export const validateCommand = async ({ try { validatePublisher(publisherJson); - console.info(`Publisher JSON valid!`); } catch (e) { const errorMsg = (e as Error | null)?.message ?? ""; showMessage( @@ -55,7 +54,6 @@ export const validateCommand = async ({ try { validateApp(appJson); - console.info(`App JSON valid!`); } catch (e) { const errorMsg = (e as Error | null)?.message ?? ""; showMessage( @@ -86,7 +84,6 @@ export const validateCommand = async ({ try { validateRelease(JSON.parse(objStringified)); - console.info(`Release JSON valid!`); } catch (e) { const errorMsg = (e as Error | null)?.message ?? ""; showMessage( diff --git a/packages/cli/src/config/PublishDetails.ts b/packages/cli/src/config/PublishDetails.ts index 8a87241..027c00e 100644 --- a/packages/cli/src/config/PublishDetails.ts +++ b/packages/cli/src/config/PublishDetails.ts @@ -18,6 +18,7 @@ import { Constants, showMessage } from "../CliUtils.js"; import util from "util"; import { imageSize } from "image-size"; import { exec } from "child_process"; +import getVideoDimensions from "get-video-dimensions"; const runImgSize = util.promisify(imageSize); const runExec = util.promisify(exec); @@ -125,19 +126,45 @@ export const loadPublishDetailsWithChecks = async ( } config.release.media.forEach((item: PublishDetails["release"]["media"][0]) => { - const imagePath = path.join(process.cwd(), item.uri); - if (!fs.existsSync(imagePath) || !checkImageExtension(imagePath)) { - throw new Error(`Invalid media path or file type: ${item.uri}. Please ensure the file is a jpeg, png, or webp file.`); + const mediaPath = path.join(process.cwd(), item.uri); + if (!fs.existsSync(mediaPath)) { + throw new Error(`File doesnt exist: ${item.uri}.`) + } + + if (item.purpose == "screenshot" && !checkImageExtension(mediaPath)) { + throw new Error(`Please ensure the file ${item.uri} is a jpeg, png, or webp file.`) + } + + if (item.purpose == "video" && !checkVideoExtension(mediaPath)) { + throw new Error(`Please ensure the file ${item.uri} is a mp4.`) } } ); - const previewMediaFiles = config.release.media?.filter( - (asset: any) => asset.purpose === "screenshot" || asset.purpose === "video" + const screenshots = config.release.media?.filter( + (asset: any) => asset.purpose === "screenshot" + ) + + for (const item of screenshots) { + const mediaPath = path.join(process.cwd(), item.uri); + if (await checkScreenshotSize(mediaPath)) { + throw new Error(`Screenshot ${mediaPath} must be at least 1080px in width and height.`); + } + } + + const videos = config.release.media?.filter( + (asset: any) => asset.purpose === "video" ) - if (previewMediaFiles.length < 4) { - throw new Error(`At least 4 screenshots or videos are required for publishing a new release. Found only ${previewMediaFiles.length}`) + for (const video of videos) { + const mediaPath = path.join(process.cwd(), video.uri); + if (await checkVideoSize(mediaPath)) { + throw new Error(`Video ${mediaPath} must be at least 720px in width and height.`); + } + } + + if (screenshots.length + videos.length < 4) { + throw new Error(`At least 4 screenshots or videos are required for publishing a new release. Found only ${screenshots.length + videos.length}`) } validateLocalizableResources(config); @@ -174,6 +201,13 @@ const checkImageExtension = (uri: string): boolean => { ); }; +const checkVideoExtension = (uri: string): boolean => { + const fileExt = path.extname(uri).toLowerCase(); + return ( + fileExt == ".mp4" + ); +}; + /** * We need to pre-check some things in the localized resources before we move forward */ @@ -206,6 +240,19 @@ const checkIconDimensions = async (iconPath: string): Promise => { return size?.width != size?.height || (size?.width ?? 0) != 512; }; +const checkScreenshotSize = async (imagePath: string): Promise => { + const size = await runImgSize(imagePath); + + return (size?.width ?? 0) < 1080 || (size?.height ?? 0) < 1080; +} + +const checkVideoSize = async (imagePath: string): Promise => { + const size = await getVideoDimensions(imagePath); + + return (size?.width ?? 0) < 720 || (size?.height ?? 0) < 720; +} + + const getAndroidDetails = async ( aaptDir: string, apkPath: string diff --git a/packages/core/package.json b/packages/core/package.json index ebcd26d..6e06abe 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,6 +34,7 @@ "@types/debug": "^4.1.7", "@types/mime": "^3.0.1", "@types/node-fetch": "^2.6.2", + "get-video-dimensions": "^1.0.0", "json-schema-to-typescript": "^11.0.2", "shx": "^0.3.4" }, diff --git a/packages/core/src/create/ReleaseCore.ts b/packages/core/src/create/ReleaseCore.ts index 1ec219c..d8db9f6 100644 --- a/packages/core/src/create/ReleaseCore.ts +++ b/packages/core/src/create/ReleaseCore.ts @@ -9,6 +9,7 @@ import { Constants, mintNft } from "../CoreUtils.js"; import * as util from "util"; import { metaplexFileReplacer, validateRelease } from "../validate/CoreValidation.js"; import { imageSize } from "image-size"; +import getVideoDimensions from "get-video-dimensions"; import type { Keypair, PublicKey } from "@solana/web3.js"; import type { @@ -46,14 +47,25 @@ const getFileMetadata = async (item: Media | File) => { }; const getMediaMetadata = async (item: Media) => { - const size = await runImgSize(item.uri ?? ""); const metadata = await getFileMetadata(item); - return { - ...metadata, - width: size?.width ?? 0, - height: size?.height ?? 0, - }; + if (item.purpose == "screenshot" || item.purpose == "icon") { + const size = await runImgSize(item.uri ?? ""); + + return { + ...metadata, + width: size?.width ?? 0, + height: size?.height ?? 0, + }; + } else { + const size = await getVideoDimensions(item.uri ?? ""); + + return { + ...metadata, + width: size?.width ?? 0, + height: size?.height ?? 0, + }; + } }; export const createReleaseJson = async ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8aebc84..f4ee01a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ importers: generate-schema: specifier: ^2.6.0 version: 2.6.0 + get-video-dimensions: + specifier: ^1.0.0 + version: 1.0.0 image-size: specifier: ^1.0.2 version: 1.0.2 @@ -196,6 +199,9 @@ importers: '@types/node-fetch': specifier: ^2.6.2 version: 2.6.2 + get-video-dimensions: + specifier: ^1.0.0 + version: 1.0.0 json-schema-to-typescript: specifier: ^11.0.2 version: 11.0.2 @@ -3358,7 +3364,6 @@ packages: /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -5119,6 +5124,11 @@ packages: get-intrinsic: 1.2.1 dev: true + /get-video-dimensions@1.0.0: + resolution: {integrity: sha512-ceq/GQySbquAdStqoejWWWCqBk8bDejcR6/0CbFUlXNAPBrqDMDKZVmGHU0PpzsK0SPYhX2jd/8tkAsbkUF4tw==} + dependencies: + mz: 1.3.0 + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -6517,6 +6527,13 @@ packages: /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + /mz@1.3.0: + resolution: {integrity: sha512-x+R7YSsEySSpV5uEB+C47JTmxv+YKKNsW3W+hjvq8NbLn8ntLgYXGrR5RjQ3Fs0e7Chw8Rp/1e5eo0n5LP76cw==} + dependencies: + native-or-bluebird: 1.2.0 + thenify: 3.3.1 + thenify-all: 1.6.0 + /mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: @@ -6525,6 +6542,10 @@ packages: thenify-all: 1.6.0 dev: true + /native-or-bluebird@1.2.0: + resolution: {integrity: sha512-0SH8UubxDfe382eYiwmd12qxAbiWGzlGZv6CkMA+DPojWa/Y0oH4hE0lRtFfFgJmPQFyKXeB8XxPbZz6TvvKaQ==} + deprecated: '''native-or-bluebird'' is deprecated. Please use ''any-promise'' instead.' + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -7567,13 +7588,11 @@ packages: engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 - dev: true /thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 - dev: true /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}