From 22e52664cd5a24ece7202a5f53aa6c169c24d910 Mon Sep 17 00:00:00 2001 From: Marcus Sanatan Date: Wed, 7 Jan 2026 01:33:20 -0400 Subject: [PATCH] Asset store helper script + updated README (#521) * Remove stray .meta file * Add a new project that will do asset uploads * Add asset store uploader * refactor: Replace Debug.Log calls with McpLog helper across codebase Standardize logging by replacing direct Debug.Log/LogWarning/LogError calls with McpLog.Info/Warn/Error throughout helper classes and client registry. Affected files: - McpClientRegistry.cs - GameObjectLookup.cs - GameObjectSerializer.cs - MaterialOps.cs - McpConfigurationHelper.cs - ObjectResolver.cs - PropertyConversion.cs - UnityJsonSerializer.cs - UnityTypeResolver.cs * feat: Add Asset Store release preparation script Add prepare_unity_asset_store_release.py tool to automate Asset Store packaging: - Stages temporary copy of MCPForUnity with Asset Store-specific edits - Removes auto-popup setup window ([InitializeOnLoad] attribute) - Renames menu entry to "Local Setup Window" for clarity - Sets default HTTP base URL to hosted endpoint - Defaults transport to HTTPRemote instead of HTTPLocal - Supports dry-run mode and optional backup of existing Assets/MCPForUnity * Show gif of MCP for Unity in Action * Add shield with asset store link * Update README to have asset store page unders installation section --- .../Editor/Clients/McpClientRegistry.cs | 3 +- .../Editor/Helpers/GameObjectLookup.cs | 4 +- .../Editor/Helpers/GameObjectSerializer.cs | 30 +- MCPForUnity/Editor/Helpers/MaterialOps.cs | 82 +-- .../Editor/Helpers/McpConfigurationHelper.cs | 6 +- MCPForUnity/Editor/Helpers/ObjectResolver.cs | 11 +- .../Editor/Helpers/PropertyConversion.cs | 7 +- MCPForUnity/Editor/Helpers/VectorParsing.cs | 12 +- .../Editor/Resources/Editor/Windows.cs | 2 +- .../Resources/Scene/GameObjectResource.cs | 6 +- MCPForUnity/Editor/Tools/FindGameObjects.cs | 2 +- MCPForUnity/Editor/Tools/JsonUtil.cs | 3 +- MCPForUnity/Editor/Tools/ManageAsset.cs | 30 +- MCPForUnity/Editor/Tools/ManageComponents.cs | 6 +- MCPForUnity/Editor/Tools/ManageGameObject.cs | 88 +-- MCPForUnity/Editor/Tools/ManageScript.cs | 16 +- .../Editor/Tools/ManageScriptableObject.cs | 2 +- MCPForUnity/Editor/Tools/ReadConsole.cs | 14 +- .../Connection/McpConnectionSection.cs | 8 + .../Windows/EditorPrefs/EditorPrefsWindow.cs | 6 +- .../Serialization/UnityTypeConverters.cs | 2 +- README-zh.md | 51 +- README.md | 53 +- TestProjects/AssetStoreUploads/.gitignore | 106 +++ .../Assets/Readme.asset.meta} | 2 +- .../AssetStoreUploads/Assets/Scenes.meta | 8 + .../Assets/Scenes/SampleScene.unity | 407 +++++++++++ .../Assets/Scenes/SampleScene.unity.meta | 7 + .../AssetStoreUploads/Assets/Settings.meta | 8 + .../Settings/SampleSceneProfile.asset.meta | 8 + .../Settings/URP-Balanced-Renderer.asset.meta | 8 + .../Assets/Settings/URP-Balanced.asset.meta | 8 + .../URP-HighFidelity-Renderer.asset.meta | 8 + .../Settings/URP-HighFidelity.asset.meta | 8 + .../URP-Performant-Renderer.asset.meta | 8 + .../Assets/Settings/URP-Performant.asset.meta | 8 + .../Assets/TutorialInfo.meta | 8 + .../Assets/TutorialInfo/Icons.meta | 9 + .../Assets/TutorialInfo/Icons/URP.png | Bin 0 -> 24069 bytes .../Assets/TutorialInfo/Icons/URP.png.meta | 134 ++++ .../Assets/TutorialInfo/Layout.wlt | 654 +++++++++++++++++ .../Assets/TutorialInfo/Layout.wlt.meta | 8 + .../Assets/TutorialInfo/Scripts.meta | 9 + .../Assets/TutorialInfo/Scripts/Editor.meta | 9 + .../Scripts/Editor/ReadmeEditor.cs | 242 +++++++ .../Scripts/Editor/ReadmeEditor.cs.meta | 12 + .../Assets/TutorialInfo/Scripts/Readme.cs | 16 + .../TutorialInfo/Scripts/Readme.cs.meta | 12 + ...salRenderPipelineGlobalSettings.asset.meta | 8 + .../com.unity.asset-store-tools/CHANGELOG.md | 324 +++++++++ .../CHANGELOG.md.meta | 7 + .../com.unity.asset-store-tools/Editor.meta | 8 + .../Editor/Api.meta | 8 + .../Editor/Api/Abstractions.meta | 8 + .../Api/Abstractions/AuthenticationBase.cs | 48 ++ .../Abstractions/AuthenticationBase.cs.meta | 11 + .../Editor/Api/Abstractions/IAssetStoreApi.cs | 21 + .../Api/Abstractions/IAssetStoreApi.cs.meta | 11 + .../Api/Abstractions/IAssetStoreClient.cs | 18 + .../Abstractions/IAssetStoreClient.cs.meta | 11 + .../Api/Abstractions/IAuthenticationType.cs | 11 + .../Abstractions/IAuthenticationType.cs.meta | 11 + .../Api/Abstractions/IPackageUploader.cs | 12 + .../Api/Abstractions/IPackageUploader.cs.meta | 11 + .../Api/Abstractions/PackageUploaderBase.cs | 59 ++ .../Abstractions/PackageUploaderBase.cs.meta | 11 + .../Editor/Api/ApiUtility.cs | 76 ++ .../Editor/Api/ApiUtility.cs.meta | 11 + .../Editor/Api/AssetStoreApi.cs | 268 +++++++ .../Editor/Api/AssetStoreApi.cs.meta | 11 + .../Editor/Api/AssetStoreClient.cs | 55 ++ .../Editor/Api/AssetStoreClient.cs.meta | 11 + .../Editor/Api/CloudTokenAuthentication.cs | 25 + .../Api/CloudTokenAuthentication.cs.meta | 11 + .../Editor/Api/CredentialsAuthentication.cs | 26 + .../Api/CredentialsAuthentication.cs.meta | 11 + .../Editor/Api/Models.meta | 8 + .../Editor/Api/Models/Category.cs | 58 ++ .../Editor/Api/Models/Category.cs.meta | 11 + .../Editor/Api/Models/Package.cs | 80 +++ .../Editor/Api/Models/Package.cs.meta | 11 + .../Api/Models/PackageAdditionalData.cs | 41 ++ .../Api/Models/PackageAdditionalData.cs.meta | 11 + .../Editor/Api/Models/User.cs | 51 ++ .../Editor/Api/Models/User.cs.meta | 11 + .../Editor/Api/Responses.meta | 8 + .../Api/Responses/AssetStoreResponse.cs | 45 ++ .../Api/Responses/AssetStoreResponse.cs.meta | 11 + .../AssetStoreToolsVersionResponse.cs | 38 + .../AssetStoreToolsVersionResponse.cs.meta | 11 + .../Api/Responses/AuthenticationResponse.cs | 74 ++ .../Responses/AuthenticationResponse.cs.meta | 11 + .../Api/Responses/CategoryDataResponse.cs | 43 ++ .../Responses/CategoryDataResponse.cs.meta | 11 + .../Api/Responses/PackageThumbnailResponse.cs | 31 + .../PackageThumbnailResponse.cs.meta | 11 + ...PackageUploadedUnityVersionDataResponse.cs | 44 ++ ...geUploadedUnityVersionDataResponse.cs.meta | 11 + .../PackagesAdditionalDataResponse.cs | 59 ++ .../PackagesAdditionalDataResponse.cs.meta | 11 + .../Api/Responses/PackagesDataResponse.cs | 59 ++ .../Responses/PackagesDataResponse.cs.meta | 11 + .../Responses/RefreshedPackageDataResponse.cs | 12 + .../RefreshedPackageDataResponse.cs.meta | 11 + .../Editor/Api/Responses/UploadResponse.cs | 28 + .../Api/Responses/UploadResponse.cs.meta | 11 + .../Editor/Api/SessionAuthentication.cs | 25 + .../Editor/Api/SessionAuthentication.cs.meta | 11 + .../Editor/Api/UnityPackageUploader.cs | 99 +++ .../Editor/Api/UnityPackageUploader.cs.meta | 11 + .../Editor/Api/UploadStatus.cs | 11 + .../Editor/Api/UploadStatus.cs.meta | 11 + .../Editor/AssemblyInfo.cs | 5 + .../Editor/AssemblyInfo.cs.meta | 11 + .../Editor/AssetStoreTools.cs | 82 +++ .../Editor/AssetStoreTools.cs.meta | 11 + .../Editor/AssetStoreToolsWindow.cs | 23 + .../Editor/AssetStoreToolsWindow.cs.meta | 11 + .../Editor/Constants.cs | 178 +++++ .../Editor/Constants.cs.meta | 11 + .../Editor/Exporter.meta | 8 + .../Editor/Exporter/Abstractions.meta | 8 + .../Exporter/Abstractions/IPackageExporter.cs | 11 + .../Abstractions/IPackageExporter.cs.meta | 11 + .../Exporter/Abstractions/IPreviewInjector.cs | 7 + .../Abstractions/IPreviewInjector.cs.meta | 11 + .../Abstractions/PackageExporterBase.cs | 134 ++++ .../Abstractions/PackageExporterBase.cs.meta | 11 + .../Abstractions/PackageExporterSettings.cs | 7 + .../PackageExporterSettings.cs.meta | 11 + .../Exporter/DefaultExporterSettings.cs | 11 + .../Exporter/DefaultExporterSettings.cs.meta | 11 + .../Editor/Exporter/DefaultPackageExporter.cs | 304 ++++++++ .../Exporter/DefaultPackageExporter.cs.meta | 11 + .../Editor/Exporter/LegacyExporterSettings.cs | 8 + .../Exporter/LegacyExporterSettings.cs.meta | 11 + .../Editor/Exporter/LegacyPackageExporter.cs | 109 +++ .../Exporter/LegacyPackageExporter.cs.meta | 11 + .../Editor/Exporter/PackageExporterResult.cs | 13 + .../Exporter/PackageExporterResult.cs.meta | 11 + .../Editor/Exporter/PreviewInjector.cs | 41 ++ .../Editor/Exporter/PreviewInjector.cs.meta | 11 + .../Editor/Previews.meta | 8 + .../Editor/Previews/Scripts.meta | 8 + .../Editor/Previews/Scripts/Data.meta | 8 + .../Data/CustomPreviewGenerationSettings.cs | 19 + .../CustomPreviewGenerationSettings.cs.meta | 11 + .../Previews/Scripts/Data/FileNameFormat.cs | 9 + .../Scripts/Data/FileNameFormat.cs.meta | 11 + .../Previews/Scripts/Data/GenerationType.cs | 9 + .../Scripts/Data/GenerationType.cs.meta | 11 + .../Data/NativePreviewGenerationSettings.cs | 10 + .../NativePreviewGenerationSettings.cs.meta | 11 + .../Previews/Scripts/Data/PreviewDatabase.cs | 14 + .../Scripts/Data/PreviewDatabase.cs.meta | 11 + .../Previews/Scripts/Data/PreviewFormat.cs | 8 + .../Scripts/Data/PreviewFormat.cs.meta | 11 + .../Scripts/Data/PreviewGenerationResult.cs | 14 + .../Data/PreviewGenerationResult.cs.meta | 11 + .../Scripts/Data/PreviewGenerationSettings.cs | 12 + .../Data/PreviewGenerationSettings.cs.meta | 11 + .../Previews/Scripts/Data/PreviewMetadata.cs | 17 + .../Scripts/Data/PreviewMetadata.cs.meta | 11 + .../Editor/Previews/Scripts/Generators.meta | 8 + .../Previews/Scripts/Generators/Custom.meta | 8 + .../Scripts/Generators/Custom/AudioChannel.cs | 97 +++ .../Generators/Custom/AudioChannel.cs.meta | 11 + .../Custom/AudioChannelCoordinate.cs | 18 + .../Custom/AudioChannelCoordinate.cs.meta | 11 + .../Generators/Custom/Screenshotters.meta | 8 + .../Screenshotters/ISceneScreenshotter.cs | 12 + .../ISceneScreenshotter.cs.meta | 11 + .../Screenshotters/MaterialScreenshotter.cs | 32 + .../MaterialScreenshotter.cs.meta | 11 + .../Screenshotters/MeshScreenshotter.cs | 33 + .../Screenshotters/MeshScreenshotter.cs.meta | 11 + .../Screenshotters/SceneScreenshotterBase.cs | 124 ++++ .../SceneScreenshotterBase.cs.meta | 11 + .../SceneScreenshotterSettings.cs | 16 + .../SceneScreenshotterSettings.cs.meta | 11 + .../Generators/Custom/TypeGenerators.meta | 8 + .../AudioTypeGeneratorSettings.cs | 15 + .../AudioTypeGeneratorSettings.cs.meta | 11 + .../AudioTypePreviewGenerator.cs | 207 ++++++ .../AudioTypePreviewGenerator.cs.meta | 11 + .../TypeGenerators/ITypePreviewGenerator.cs | 16 + .../ITypePreviewGenerator.cs.meta | 11 + .../MaterialTypePreviewGenerator.cs | 85 +++ .../MaterialTypePreviewGenerator.cs.meta | 11 + .../ModelTypePreviewGenerator.cs | 86 +++ .../ModelTypePreviewGenerator.cs.meta | 11 + .../PrefabTypePreviewGenerator.cs | 113 +++ .../PrefabTypePreviewGenerator.cs.meta | 11 + .../TextureTypeGeneratorSettings.cs | 11 + .../TextureTypeGeneratorSettings.cs.meta | 11 + .../TextureTypePreviewGenerator.cs | 116 +++ .../TextureTypePreviewGenerator.cs.meta | 11 + .../TypeGenerators/TypeGeneratorSettings.cs | 12 + .../TypeGeneratorSettings.cs.meta | 11 + .../TypePreviewGeneratorBase.cs | 126 ++++ .../TypePreviewGeneratorBase.cs.meta | 11 + .../TypePreviewGeneratorFromScene.cs | 111 +++ .../TypePreviewGeneratorFromScene.cs.meta | 11 + .../TypePreviewGeneratorFromSceneSettings.cs | 9 + ...ePreviewGeneratorFromSceneSettings.cs.meta | 11 + .../Generators/CustomPreviewGenerator.cs | 213 ++++++ .../Generators/CustomPreviewGenerator.cs.meta | 11 + .../Scripts/Generators/IPreviewGenerator.cs | 15 + .../Generators/IPreviewGenerator.cs.meta | 11 + .../Generators/NativePreviewGenerator.cs | 362 ++++++++++ .../Generators/NativePreviewGenerator.cs.meta | 11 + .../Generators/PreviewGeneratorBase.cs | 45 ++ .../Generators/PreviewGeneratorBase.cs.meta | 11 + .../Editor/Previews/Scripts/Services.meta | 8 + .../Previews/Scripts/Services/Caching.meta | 8 + .../Services/Caching/CachingService.cs | 87 +++ .../Services/Caching/CachingService.cs.meta | 11 + .../Services/Caching/ICachingService.cs | 11 + .../Services/Caching/ICachingService.cs.meta | 11 + .../Scripts/Services/IPreviewService.cs | 4 + .../Scripts/Services/IPreviewService.cs.meta | 11 + .../Services/PreviewServiceProvider.cs | 17 + .../Services/PreviewServiceProvider.cs.meta | 11 + .../Editor/Previews/Scripts/UI.meta | 8 + .../Editor/Previews/Scripts/UI/Data.meta | 8 + .../Previews/Scripts/UI/Data/AssetPreview.cs | 56 ++ .../Scripts/UI/Data/AssetPreview.cs.meta | 11 + .../Scripts/UI/Data/AssetPreviewCollection.cs | 46 ++ .../UI/Data/AssetPreviewCollection.cs.meta | 11 + .../Previews/Scripts/UI/Data/IAssetPreview.cs | 13 + .../Scripts/UI/Data/IAssetPreview.cs.meta | 11 + .../UI/Data/IAssetPreviewCollection.cs | 15 + .../UI/Data/IAssetPreviewCollection.cs.meta | 11 + .../UI/Data/IPreviewGeneratorSettings.cs | 27 + .../UI/Data/IPreviewGeneratorSettings.cs.meta | 11 + .../UI/Data/PreviewGeneratorSettings.cs | 212 ++++++ .../UI/Data/PreviewGeneratorSettings.cs.meta | 11 + .../Editor/Previews/Scripts/UI/Elements.meta | 8 + .../UI/Elements/AssetPreviewElement.cs | 83 +++ .../UI/Elements/AssetPreviewElement.cs.meta | 11 + .../Scripts/UI/Elements/GridListElement.cs | 140 ++++ .../UI/Elements/GridListElement.cs.meta | 11 + .../UI/Elements/PreviewCollectionElement.cs | 116 +++ .../Elements/PreviewCollectionElement.cs.meta | 11 + .../Elements/PreviewGenerateButtonElement.cs | 50 ++ .../PreviewGenerateButtonElement.cs.meta | 11 + .../Elements/PreviewGeneratorPathsElement.cs | 122 ++++ .../PreviewGeneratorPathsElement.cs.meta | 11 + .../PreviewGeneratorSettingsElement.cs | 99 +++ .../PreviewGeneratorSettingsElement.cs.meta | 11 + .../PreviewWindowDescriptionElement.cs | 87 +++ .../PreviewWindowDescriptionElement.cs.meta | 11 + .../Scripts/UI/PreviewGeneratorWindow.cs | 51 ++ .../Scripts/UI/PreviewGeneratorWindow.cs.meta | 11 + .../Editor/Previews/Scripts/UI/Views.meta | 8 + .../Scripts/UI/Views/PreviewListView.cs | 142 ++++ .../Scripts/UI/Views/PreviewListView.cs.meta | 11 + .../Editor/Previews/Scripts/Utility.meta | 8 + .../Scripts/Utility/GraphicsUtility.cs | 96 +++ .../Scripts/Utility/GraphicsUtility.cs.meta | 11 + .../Scripts/Utility/PreviewConvertUtility.cs | 72 ++ .../Utility/PreviewConvertUtility.cs.meta | 11 + .../Scripts/Utility/PreviewSceneUtility.cs | 196 ++++++ .../Utility/PreviewSceneUtility.cs.meta | 11 + .../Scripts/Utility/RenderPipeline.cs | 10 + .../Scripts/Utility/RenderPipeline.cs.meta | 11 + .../Scripts/Utility/RenderPipelineUtility.cs | 32 + .../Utility/RenderPipelineUtility.cs.meta | 11 + .../Editor/Previews/Styles.meta | 8 + .../Editor/Previews/Styles/Style.uss | 210 ++++++ .../Editor/Previews/Styles/Style.uss.meta | 11 + .../Editor/Previews/Styles/ThemeDark.uss | 67 ++ .../Editor/Previews/Styles/ThemeDark.uss.meta | 11 + .../Editor/Previews/Styles/ThemeLight.uss | 67 ++ .../Previews/Styles/ThemeLight.uss.meta | 11 + .../Unity.AssetStoreTools.Editor.asmdef | 36 + .../Unity.AssetStoreTools.Editor.asmdef.meta | 7 + .../Editor/Uploader.meta | 8 + .../Editor/Uploader/Icons.meta | 8 + .../Editor/Uploader/Icons/account-dark.png | Bin 0 -> 337 bytes .../Uploader/Icons/account-dark.png.meta | 123 ++++ .../Editor/Uploader/Icons/account-light.png | Bin 0 -> 387 bytes .../Uploader/Icons/account-light.png.meta | 123 ++++ .../Editor/Uploader/Icons/open-in-browser.png | Bin 0 -> 878 bytes .../Uploader/Icons/open-in-browser.png.meta | 147 ++++ .../Uploader/Icons/publisher-portal-dark.png | Bin 0 -> 33588 bytes .../Icons/publisher-portal-dark.png.meta | 128 ++++ .../Uploader/Icons/publisher-portal-light.png | Bin 0 -> 33281 bytes .../Icons/publisher-portal-light.png.meta | 135 ++++ .../Editor/Uploader/Scripts.meta | 8 + .../Editor/Uploader/Scripts/Data.meta | 8 + .../Uploader/Scripts/Data/Abstractions.meta | 8 + .../Scripts/Data/Abstractions/IPackage.cs | 34 + .../Data/Abstractions/IPackage.cs.meta | 11 + .../Data/Abstractions/IPackageContent.cs | 14 + .../Data/Abstractions/IPackageContent.cs.meta | 11 + .../Data/Abstractions/IPackageGroup.cs | 17 + .../Data/Abstractions/IPackageGroup.cs.meta | 11 + .../Scripts/Data/Abstractions/IWorkflow.cs | 36 + .../Data/Abstractions/IWorkflow.cs.meta | 11 + .../Data/Abstractions/IWorkflowServices.cs | 18 + .../Abstractions/IWorkflowServices.cs.meta | 11 + .../Scripts/Data/Abstractions/WorkflowBase.cs | 253 +++++++ .../Data/Abstractions/WorkflowBase.cs.meta | 11 + .../Uploader/Scripts/Data/AssetsWorkflow.cs | 329 +++++++++ .../Scripts/Data/AssetsWorkflow.cs.meta | 11 + .../Scripts/Data/HybridPackageWorkflow.cs | 251 +++++++ .../Data/HybridPackageWorkflow.cs.meta | 11 + .../Editor/Uploader/Scripts/Data/Package.cs | 91 +++ .../Uploader/Scripts/Data/Package.cs.meta | 11 + .../Uploader/Scripts/Data/PackageContent.cs | 68 ++ .../Scripts/Data/PackageContent.cs.meta | 11 + .../Uploader/Scripts/Data/PackageGroup.cs | 64 ++ .../Scripts/Data/PackageGroup.cs.meta | 11 + .../Uploader/Scripts/Data/PackageSorting.cs | 9 + .../Scripts/Data/PackageSorting.cs.meta | 11 + .../Uploader/Scripts/Data/Serialization.meta | 8 + .../Scripts/Data/Serialization/AssetPath.cs | 59 ++ .../Data/Serialization/AssetPath.cs.meta | 11 + .../Serialization/AssetsWorkflowStateData.cs | 77 ++ .../AssetsWorkflowStateData.cs.meta | 11 + .../HybridPackageWorkflowState.cs | 41 ++ .../HybridPackageWorkflowState.cs.meta | 11 + .../UnityPackageWorkflowStateData.cs | 25 + .../UnityPackageWorkflowStateData.cs.meta | 11 + .../Data/Serialization/WorkflowStateData.cs | 68 ++ .../Serialization/WorkflowStateData.cs.meta | 11 + .../Scripts/Data/UnityPackageWorkflow.cs | 135 ++++ .../Scripts/Data/UnityPackageWorkflow.cs.meta | 11 + .../Uploader/Scripts/Data/WorkflowServices.cs | 53 ++ .../Scripts/Data/WorkflowServices.cs.meta | 11 + .../Editor/Uploader/Scripts/Services.meta | 8 + .../Uploader/Scripts/Services/Analytics.meta | 8 + .../Services/Analytics/AnalyticsService.cs | 45 ++ .../Analytics/AnalyticsService.cs.meta | 11 + .../Scripts/Services/Analytics/Data.meta | 8 + .../Analytics/Data/AuthenticationAnalytic.cs | 46 ++ .../Data/AuthenticationAnalytic.cs.meta | 11 + .../Services/Analytics/Data/BaseAnalytic.cs | 35 + .../Analytics/Data/BaseAnalytic.cs.meta | 11 + .../Analytics/Data/IAssetStoreAnalytic.cs | 16 + .../Data/IAssetStoreAnalytic.cs.meta | 11 + .../Analytics/Data/IAssetStoreAnalyticData.cs | 8 + .../Data/IAssetStoreAnalyticData.cs.meta | 11 + .../Analytics/Data/PackageUploadAnalytic.cs | 72 ++ .../Data/PackageUploadAnalytic.cs.meta | 11 + .../Data/ValidationResultsSerializer.cs | 91 +++ .../Data/ValidationResultsSerializer.cs.meta | 11 + .../Services/Analytics/IAnalyticsService.cs | 10 + .../Analytics/IAnalyticsService.cs.meta | 11 + .../Editor/Uploader/Scripts/Services/Api.meta | 8 + .../Services/Api/AuthenticationService.cs | 100 +++ .../Api/AuthenticationService.cs.meta | 11 + .../Services/Api/IAuthenticationService.cs | 16 + .../Api/IAuthenticationService.cs.meta | 11 + .../Api/IPackageDownloadingService.cs | 16 + .../Api/IPackageDownloadingService.cs.meta | 11 + .../Services/Api/IPackageUploadingService.cs | 16 + .../Api/IPackageUploadingService.cs.meta | 11 + .../Services/Api/PackageDownloadingService.cs | 109 +++ .../Api/PackageDownloadingService.cs.meta | 11 + .../Services/Api/PackageUploadingService.cs | 103 +++ .../Api/PackageUploadingService.cs.meta | 11 + .../Uploader/Scripts/Services/Caching.meta | 8 + .../Services/Caching/CachingService.cs | 157 +++++ .../Services/Caching/CachingService.cs.meta | 11 + .../Services/Caching/ICachingService.cs | 25 + .../Services/Caching/ICachingService.cs.meta | 11 + .../Scripts/Services/IUploaderService.cs | 4 + .../Scripts/Services/IUploaderService.cs.meta | 11 + .../Scripts/Services/PackageFactory.meta | 8 + .../PackageFactory/IPackageFactoryService.cs | 18 + .../IPackageFactoryService.cs.meta | 11 + .../PackageFactory/PackageFactoryService.cs | 95 +++ .../PackageFactoryService.cs.meta | 11 + .../Services/UploaderServiceProvider.cs | 26 + .../Services/UploaderServiceProvider.cs.meta | 11 + .../Editor/Uploader/Scripts/UI.meta | 8 + .../Editor/Uploader/Scripts/UI/Elements.meta | 8 + .../Scripts/UI/Elements/Abstractions.meta | 8 + .../Abstractions/ValidationElementBase.cs | 170 +++++ .../ValidationElementBase.cs.meta | 11 + .../Abstractions/WorkflowElementBase.cs | 151 ++++ .../Abstractions/WorkflowElementBase.cs.meta | 11 + .../Scripts/UI/Elements/AccountToolbar.cs | 102 +++ .../UI/Elements/AccountToolbar.cs.meta | 11 + .../UI/Elements/AssetsWorkflowElement.cs | 215 ++++++ .../UI/Elements/AssetsWorkflowElement.cs.meta | 11 + .../CurrentProjectValidationElement.cs | 31 + .../CurrentProjectValidationElement.cs.meta | 11 + .../ExternalProjectValidationElement.cs | 92 +++ .../ExternalProjectValidationElement.cs.meta | 11 + .../Elements/HybridPackageWorkflowElement.cs | 116 +++ .../HybridPackageWorkflowElement.cs.meta | 11 + .../Scripts/UI/Elements/LoadingSpinner.cs | 52 ++ .../UI/Elements/LoadingSpinner.cs.meta | 11 + .../Elements/MultiToggleSelectionElement.cs | 187 +++++ .../MultiToggleSelectionElement.cs.meta | 11 + .../UI/Elements/PackageContentElement.cs | 137 ++++ .../UI/Elements/PackageContentElement.cs.meta | 11 + .../Scripts/UI/Elements/PackageElement.cs | 215 ++++++ .../UI/Elements/PackageElement.cs.meta | 11 + .../UI/Elements/PackageGroupElement.cs | 149 ++++ .../UI/Elements/PackageGroupElement.cs.meta | 11 + .../Scripts/UI/Elements/PackageListToolbar.cs | 58 ++ .../UI/Elements/PackageListToolbar.cs.meta | 11 + .../UI/Elements/PackageUploadElement.cs | 321 +++++++++ .../UI/Elements/PackageUploadElement.cs.meta | 11 + .../UI/Elements/PathSelectionElement.cs | 63 ++ .../UI/Elements/PathSelectionElement.cs.meta | 11 + .../UI/Elements/PreviewGenerationElement.cs | 109 +++ .../Elements/PreviewGenerationElement.cs.meta | 11 + .../Elements/UnityPackageWorkflowElement.cs | 58 ++ .../UnityPackageWorkflowElement.cs.meta | 11 + .../Editor/Uploader/Scripts/UI/Views.meta | 8 + .../Uploader/Scripts/UI/Views/LoginView.cs | 284 ++++++++ .../Scripts/UI/Views/LoginView.cs.meta | 11 + .../Scripts/UI/Views/PackageListView.cs | 249 +++++++ .../Scripts/UI/Views/PackageListView.cs.meta | 11 + .../Editor/Uploader/Styles.meta | 8 + .../Editor/Uploader/Styles/LoginView.meta | 8 + .../Uploader/Styles/LoginView/Style.uss | 134 ++++ .../Uploader/Styles/LoginView/Style.uss.meta | 11 + .../Uploader/Styles/LoginView/ThemeDark.uss | 44 ++ .../Styles/LoginView/ThemeDark.uss.meta | 11 + .../Uploader/Styles/LoginView/ThemeLight.uss | 44 ++ .../Styles/LoginView/ThemeLight.uss.meta | 11 + .../Uploader/Styles/PackageListView.meta | 8 + .../Uploader/Styles/PackageListView/Style.uss | 485 +++++++++++++ .../Styles/PackageListView/Style.uss.meta | 11 + .../Styles/PackageListView/ThemeDark.uss | 238 +++++++ .../Styles/PackageListView/ThemeDark.uss.meta | 11 + .../Styles/PackageListView/ThemeLight.uss | 238 +++++++ .../PackageListView/ThemeLight.uss.meta | 11 + .../Editor/Uploader/Styles/Style.uss | 74 ++ .../Editor/Uploader/Styles/Style.uss.meta | 11 + .../Editor/Uploader/Styles/ThemeDark.uss | 9 + .../Editor/Uploader/Styles/ThemeDark.uss.meta | 11 + .../Editor/Uploader/Styles/ThemeLight.uss | 9 + .../Uploader/Styles/ThemeLight.uss.meta | 11 + .../Editor/Uploader/UploaderWindow.cs | 232 ++++++ .../Editor/Uploader/UploaderWindow.cs.meta | 11 + .../Editor/Utility.meta | 8 + .../Editor/Utility/ASDebug.cs | 65 ++ .../Editor/Utility/ASDebug.cs.meta | 11 + .../Editor/Utility/ASToolsPreferences.cs | 134 ++++ .../Editor/Utility/ASToolsPreferences.cs.meta | 11 + .../Editor/Utility/ASToolsUpdater.cs | 250 +++++++ .../Editor/Utility/ASToolsUpdater.cs.meta | 11 + .../Editor/Utility/CacheUtil.cs | 266 +++++++ .../Editor/Utility/CacheUtil.cs.meta | 11 + .../Editor/Utility/FileUtility.cs | 207 ++++++ .../Editor/Utility/FileUtility.cs.meta | 11 + .../Editor/Utility/LegacyToolsRemover.cs | 86 +++ .../Editor/Utility/LegacyToolsRemover.cs.meta | 11 + .../Editor/Utility/PackageUtility.cs | 170 +++++ .../Editor/Utility/PackageUtility.cs.meta | 11 + .../Editor/Utility/ServiceProvider.cs | 113 +++ .../Editor/Utility/ServiceProvider.cs.meta | 11 + .../Editor/Utility/StyleSelector.cs | 55 ++ .../Editor/Utility/StyleSelector.cs.meta | 11 + .../Editor/Utility/Styles.meta | 8 + .../Editor/Utility/Styles/Updater.meta | 8 + .../Editor/Utility/Styles/Updater/Style.uss | 76 ++ .../Utility/Styles/Updater/Style.uss.meta | 11 + .../Utility/Styles/Updater/ThemeDark.uss | 3 + .../Utility/Styles/Updater/ThemeDark.uss.meta | 11 + .../Utility/Styles/Updater/ThemeLight.uss | 3 + .../Styles/Updater/ThemeLight.uss.meta | 11 + .../Editor/Utility/SymlinkUtil.cs | 67 ++ .../Editor/Utility/SymlinkUtil.cs.meta | 3 + .../Editor/Validator.meta | 8 + .../Editor/Validator/Icons.meta | 8 + .../Editor/Validator/Icons/error.png | Bin 0 -> 1057 bytes .../Editor/Validator/Icons/error.png.meta | 128 ++++ .../Editor/Validator/Icons/error_d.png | Bin 0 -> 1024 bytes .../Editor/Validator/Icons/error_d.png.meta | 128 ++++ .../Editor/Validator/Icons/success.png | Bin 0 -> 1583 bytes .../Editor/Validator/Icons/success.png.meta | 128 ++++ .../Editor/Validator/Icons/success_d.png | Bin 0 -> 1617 bytes .../Editor/Validator/Icons/success_d.png.meta | 128 ++++ .../Editor/Validator/Icons/undefined.png | Bin 0 -> 1561 bytes .../Editor/Validator/Icons/undefined.png.meta | 128 ++++ .../Editor/Validator/Icons/undefined_d.png | Bin 0 -> 1600 bytes .../Validator/Icons/undefined_d.png.meta | 128 ++++ .../Editor/Validator/Icons/warning.png | Bin 0 -> 1141 bytes .../Editor/Validator/Icons/warning.png.meta | 128 ++++ .../Editor/Validator/Icons/warning_d.png | Bin 0 -> 1185 bytes .../Editor/Validator/Icons/warning_d.png.meta | 128 ++++ .../Editor/Validator/Scripts.meta | 8 + .../Editor/Validator/Scripts/Categories.meta | 3 + .../Scripts/Categories/CategoryEvaluator.cs | 52 ++ .../Categories/CategoryEvaluator.cs.meta | 3 + .../Scripts/Categories/ValidatorCategory.cs | 38 + .../Categories/ValidatorCategory.cs.meta | 3 + .../Scripts/CurrentProjectValidator.cs | 71 ++ .../Scripts/CurrentProjectValidator.cs.meta | 11 + .../Editor/Validator/Scripts/Data.meta | 8 + .../Data/CurrentProjectValidationSettings.cs | 33 + .../CurrentProjectValidationSettings.cs.meta | 11 + .../Data/ExternalProjectValidationSettings.cs | 7 + .../ExternalProjectValidationSettings.cs.meta | 11 + .../Scripts/Data/MessageActions.meta | 8 + .../MessageActions/HighlightObjectAction.cs | 31 + .../HighlightObjectAction.cs.meta | 11 + .../Data/MessageActions/IMessageAction.cs | 16 + .../MessageActions/IMessageAction.cs.meta | 11 + .../Data/MessageActions/OpenAssetAction.cs | 38 + .../MessageActions/OpenAssetAction.cs.meta | 11 + .../Validator/Scripts/Data/TestResult.cs | 52 ++ .../Validator/Scripts/Data/TestResult.cs.meta | 11 + .../Scripts/Data/TestResultMessage.cs | 53 ++ .../Scripts/Data/TestResultMessage.cs.meta | 11 + .../Scripts/Data/TestResultObject.cs | 35 + .../Scripts/Data/TestResultObject.cs.meta | 11 + .../Scripts/Data/TestResultStatus.cs | 11 + .../Scripts/Data/TestResultStatus.cs.meta | 11 + .../Scripts/Data/ValidationResult.cs | 24 + .../Scripts/Data/ValidationResult.cs.meta | 11 + .../Scripts/Data/ValidationSettings.cs | 7 + .../Scripts/Data/ValidationSettings.cs.meta | 11 + .../Scripts/Data/ValidationStatus.cs | 10 + .../Scripts/Data/ValidationStatus.cs.meta | 11 + .../Validator/Scripts/Data/ValidationType.cs | 8 + .../Scripts/Data/ValidationType.cs.meta | 11 + .../Scripts/ExternalProjectValidator.cs | 259 +++++++ .../Scripts/ExternalProjectValidator.cs.meta | 11 + .../Editor/Validator/Scripts/IValidator.cs | 11 + .../Validator/Scripts/IValidator.cs.meta | 11 + .../Editor/Validator/Scripts/Services.meta | 8 + .../Scripts/Services/CachingService.meta | 8 + .../Services/CachingService/CachingService.cs | 55 ++ .../CachingService/CachingService.cs.meta | 11 + .../CachingService/ICachingService.cs | 11 + .../CachingService/ICachingService.cs.meta | 11 + .../PreviewDatabaseContractResolver.cs | 26 + .../PreviewDatabaseContractResolver.cs.meta | 11 + .../Scripts/Services/IValidatorService.cs | 4 + .../Services/IValidatorService.cs.meta | 11 + .../Scripts/Services/Validation.meta | 8 + .../Services/Validation/Abstractions.meta | 8 + .../Abstractions/IAssetUtilityService.cs | 18 + .../Abstractions/IAssetUtilityService.cs.meta | 11 + .../IFileSignatureUtilityService.cs | 7 + .../IFileSignatureUtilityService.cs.meta | 11 + .../Abstractions/IMeshUtilityService.cs | 10 + .../Abstractions/IMeshUtilityService.cs.meta | 11 + .../Abstractions/IModelUtilityService.cs | 10 + .../Abstractions/IModelUtilityService.cs.meta | 11 + .../Abstractions/ISceneUtilityService.cs | 13 + .../Abstractions/ISceneUtilityService.cs.meta | 11 + .../Abstractions/IScriptUtilityService.cs | 14 + .../IScriptUtilityService.cs.meta | 11 + .../Validation/AssetUtilityService.cs | 216 ++++++ .../Validation/AssetUtilityService.cs.meta | 11 + .../Scripts/Services/Validation/Data.meta | 8 + .../Services/Validation/Data/ArchiveType.cs | 19 + .../Validation/Data/ArchiveType.cs.meta | 11 + .../Validation/Data/AssetEnumerator.cs | 84 +++ .../Validation/Data/AssetEnumerator.cs.meta | 11 + .../Services/Validation/Data/AssetType.cs | 25 + .../Validation/Data/AssetType.cs.meta | 11 + .../Services/Validation/Data/LogEntry.cs | 10 + .../Services/Validation/Data/LogEntry.cs.meta | 11 + .../Validation/FileSignatureUtilityService.cs | 78 +++ .../FileSignatureUtilityService.cs.meta | 11 + .../Services/Validation/MeshUtilityService.cs | 26 + .../Validation/MeshUtilityService.cs.meta | 11 + .../Validation/ModelUtilityService.cs | 147 ++++ .../Validation/ModelUtilityService.cs.meta | 11 + .../Validation/SceneUtilityService.cs | 26 + .../Validation/SceneUtilityService.cs.meta | 11 + .../Validation/ScriptUtilityService.cs | 658 ++++++++++++++++++ .../Validation/ScriptUtilityService.cs.meta | 11 + .../Services/ValidatorServiceProvider.cs | 24 + .../Services/ValidatorServiceProvider.cs.meta | 11 + .../Validator/Scripts/Test Definitions.meta | 8 + .../Scripts/Test Definitions/AutomatedTest.cs | 121 ++++ .../Test Definitions/AutomatedTest.cs.meta | 11 + .../Test Definitions/GenericTestConfig.cs | 7 + .../GenericTestConfig.cs.meta | 11 + .../Scripts/Test Definitions/ITestConfig.cs | 4 + .../Test Definitions/ITestConfig.cs.meta | 11 + .../Scripts/Test Definitions/ITestScript.cs | 9 + .../Test Definitions/ITestScript.cs.meta | 11 + .../Test Definitions/Scriptable Objects.meta | 8 + .../AutomatedTestScriptableObject.cs | 11 + .../AutomatedTestScriptableObject.cs.meta | 11 + .../Scriptable Objects/Editor.meta | 8 + ...ValidationTestScriptableObjectInspector.cs | 196 ++++++ ...ationTestScriptableObjectInspector.cs.meta | 11 + .../ValidationTestScriptableObject.cs | 35 + .../ValidationTestScriptableObject.cs.meta | 11 + .../Test Definitions/ValidationTest.cs | 38 + .../Test Definitions/ValidationTest.cs.meta | 11 + .../Validator/Scripts/Test Methods.meta | 8 + .../Scripts/Test Methods/Generic.meta | 8 + .../Generic/CheckAnimationClips.cs | 64 ++ .../Generic/CheckAnimationClips.cs.meta | 11 + .../Generic/CheckAudioClipping.cs | 128 ++++ .../Generic/CheckAudioClipping.cs.meta | 11 + .../Test Methods/Generic/CheckColliders.cs | 55 ++ .../Generic/CheckColliders.cs.meta | 11 + .../Generic/CheckCompressedFiles.cs | 121 ++++ .../Generic/CheckCompressedFiles.cs.meta | 11 + .../Test Methods/Generic/CheckEmptyPrefabs.cs | 46 ++ .../Generic/CheckEmptyPrefabs.cs.meta | 11 + .../Generic/CheckFileMenuNames.cs | 153 ++++ .../Generic/CheckFileMenuNames.cs.meta | 11 + .../Scripts/Test Methods/Generic/CheckLODs.cs | 79 +++ .../Test Methods/Generic/CheckLODs.cs.meta | 11 + .../Test Methods/Generic/CheckLineEndings.cs | 77 ++ .../Generic/CheckLineEndings.cs.meta | 11 + .../Test Methods/Generic/CheckMeshPrefabs.cs | 106 +++ .../Generic/CheckMeshPrefabs.cs.meta | 11 + .../Generic/CheckMissingComponentsinAssets.cs | 66 ++ .../CheckMissingComponentsinAssets.cs.meta | 11 + .../Generic/CheckMissingComponentsinScenes.cs | 93 +++ .../CheckMissingComponentsinScenes.cs.meta | 11 + .../Generic/CheckModelImportLogs.cs | 64 ++ .../Generic/CheckModelImportLogs.cs.meta | 11 + .../Generic/CheckModelOrientation.cs | 71 ++ .../Generic/CheckModelOrientation.cs.meta | 11 + .../Test Methods/Generic/CheckModelTypes.cs | 58 ++ .../Generic/CheckModelTypes.cs.meta | 11 + .../Generic/CheckNormalMapTextures.cs | 94 +++ .../Generic/CheckNormalMapTextures.cs.meta | 11 + .../Generic/CheckPackageNaming.cs | 295 ++++++++ .../Generic/CheckPackageNaming.cs.meta | 11 + .../Generic/CheckParticleSystems.cs | 76 ++ .../Generic/CheckParticleSystems.cs.meta | 11 + .../Test Methods/Generic/CheckPathLengths.cs | 98 +++ .../Generic/CheckPathLengths.cs.meta | 11 + .../Generic/CheckPrefabTransforms.cs | 71 ++ .../Generic/CheckPrefabTransforms.cs.meta | 11 + .../Generic/CheckScriptCompilation.cs | 30 + .../Generic/CheckScriptCompilation.cs.meta | 11 + .../Generic/CheckShaderCompilation.cs | 63 ++ .../Generic/CheckShaderCompilation.cs.meta | 11 + .../Generic/CheckTextureDimensions.cs | 57 ++ .../Generic/CheckTextureDimensions.cs.meta | 11 + .../Generic/CheckTypeNamespaces.cs | 233 +++++++ .../Generic/CheckTypeNamespaces.cs.meta | 11 + .../Generic/RemoveExecutableFiles.cs | 38 + .../Generic/RemoveExecutableFiles.cs.meta | 11 + .../Test Methods/Generic/RemoveJPGFiles.cs | 38 + .../Generic/RemoveJPGFiles.cs.meta | 11 + .../Generic/RemoveJavaScriptFiles.cs | 38 + .../Generic/RemoveJavaScriptFiles.cs.meta | 11 + .../Generic/RemoveLossyAudioFiles.cs | 83 +++ .../Generic/RemoveLossyAudioFiles.cs.meta | 11 + .../Test Methods/Generic/RemoveMixamoFiles.cs | 38 + .../Generic/RemoveMixamoFiles.cs.meta | 11 + .../Generic/RemoveSpeedTreeFiles.cs | 38 + .../Generic/RemoveSpeedTreeFiles.cs.meta | 11 + .../Test Methods/Generic/RemoveVideoFiles.cs | 38 + .../Generic/RemoveVideoFiles.cs.meta | 11 + .../Scripts/Test Methods/UnityPackage.meta | 8 + .../UnityPackage/CheckDemoScenes.cs | 172 +++++ .../UnityPackage/CheckDemoScenes.cs.meta | 11 + .../UnityPackage/CheckDocumentation.cs | 73 ++ .../UnityPackage/CheckDocumentation.cs.meta | 11 + .../UnityPackage/CheckPackageSize.cs | 69 ++ .../UnityPackage/CheckPackageSize.cs.meta | 11 + .../CheckProjectTemplateAssets.cs | 217 ++++++ .../CheckProjectTemplateAssets.cs.meta | 11 + .../Editor/Validator/Scripts/UI.meta | 8 + .../Editor/Validator/Scripts/UI/Data.meta | 8 + .../Scripts/UI/Data/Abstractions.meta | 8 + .../UI/Data/Abstractions/IValidatorResults.cs | 15 + .../Abstractions/IValidatorResults.cs.meta | 11 + .../Data/Abstractions/IValidatorSettings.cs | 31 + .../Abstractions/IValidatorSettings.cs.meta | 11 + .../UI/Data/Abstractions/IValidatorTest.cs | 15 + .../Data/Abstractions/IValidatorTest.cs.meta | 11 + .../Data/Abstractions/IValidatorTestGroup.cs | 12 + .../Abstractions/IValidatorTestGroup.cs.meta | 11 + .../Scripts/UI/Data/Serialization.meta | 8 + .../Data/Serialization/ValidatorStateData.cs | 28 + .../Serialization/ValidatorStateData.cs.meta | 11 + .../ValidatorStateDataContractResolver.cs | 26 + ...ValidatorStateDataContractResolver.cs.meta | 11 + .../Serialization/ValidatorStateResults.cs | 83 +++ .../ValidatorStateResults.cs.meta | 11 + .../Serialization/ValidatorStateSettings.cs | 63 ++ .../ValidatorStateSettings.cs.meta | 11 + .../Scripts/UI/Data/ValidatorResults.cs | 137 ++++ .../Scripts/UI/Data/ValidatorResults.cs.meta | 11 + .../Scripts/UI/Data/ValidatorSettings.cs | 236 +++++++ .../Scripts/UI/Data/ValidatorSettings.cs.meta | 11 + .../Scripts/UI/Data/ValidatorTest.cs | 28 + .../Scripts/UI/Data/ValidatorTest.cs.meta | 11 + .../Scripts/UI/Data/ValidatorTestGroup.cs | 18 + .../UI/Data/ValidatorTestGroup.cs.meta | 11 + .../Editor/Validator/Scripts/UI/Elements.meta | 8 + .../UI/Elements/ValidatorButtonElement.cs | 50 ++ .../Elements/ValidatorButtonElement.cs.meta | 11 + .../Elements/ValidatorDescriptionElement.cs | 114 +++ .../ValidatorDescriptionElement.cs.meta | 3 + .../UI/Elements/ValidatorPathsElement.cs | 128 ++++ .../UI/Elements/ValidatorPathsElement.cs.meta | 11 + .../UI/Elements/ValidatorResultsElement.cs | 47 ++ .../Elements/ValidatorResultsElement.cs.meta | 11 + .../UI/Elements/ValidatorSettingsElement.cs | 96 +++ .../Elements/ValidatorSettingsElement.cs.meta | 11 + .../UI/Elements/ValidatorTestElement.cs | 239 +++++++ .../UI/Elements/ValidatorTestElement.cs.meta | 11 + .../UI/Elements/ValidatorTestGroupElement.cs | 102 +++ .../ValidatorTestGroupElement.cs.meta | 11 + .../Validator/Scripts/UI/ValidatorWindow.cs | 56 ++ .../Scripts/UI/ValidatorWindow.cs.meta | 11 + .../Editor/Validator/Scripts/UI/Views.meta | 8 + .../Scripts/UI/Views/ValidatorTestsView.cs | 103 +++ .../UI/Views/ValidatorTestsView.cs.meta | 11 + .../Editor/Validator/Scripts/Utility.meta | 8 + .../Scripts/Utility/ValidatorUtility.cs | 142 ++++ .../Scripts/Utility/ValidatorUtility.cs.meta | 11 + .../Editor/Validator/Scripts/ValidatorBase.cs | 108 +++ .../Validator/Scripts/ValidatorBase.cs.meta | 11 + .../Editor/Validator/Styles.meta | 8 + .../Editor/Validator/Styles/Style.uss | 337 +++++++++ .../Editor/Validator/Styles/Style.uss.meta | 11 + .../Editor/Validator/Styles/ThemeDark.uss | 166 +++++ .../Validator/Styles/ThemeDark.uss.meta | 11 + .../Editor/Validator/Styles/ThemeLight.uss | 166 +++++ .../Validator/Styles/ThemeLight.uss.meta | 11 + .../Editor/Validator/Tests.meta | 8 + .../Editor/Validator/Tests/Generic.meta | 8 + .../Generic/Check Animation Clips.asset.meta | 8 + .../Generic/Check Audio Clipping.asset.meta | 8 + .../Tests/Generic/Check Colliders.asset.meta | 8 + .../Generic/Check Compressed Files.asset.meta | 8 + .../Generic/Check Empty Prefabs.asset.meta | 8 + .../Generic/Check File Menu Names.asset.meta | 8 + .../Tests/Generic/Check LODs.asset.meta | 8 + .../Generic/Check Line Endings.asset.meta | 8 + .../Generic/Check Mesh Prefabs.asset.meta | 8 + ...ck Missing Components in Assets.asset.meta | 8 + ...ck Missing Components in Scenes.asset.meta | 8 + .../Check Model Import Logs.asset.meta | 8 + .../Check Model Orientation.asset.meta | 8 + .../Generic/Check Model Types.asset.meta | 8 + .../Check Normal Map Textures.asset.meta | 8 + .../Generic/Check Package Naming.asset.meta | 8 + .../Generic/Check Particle Systems.asset.meta | 8 + .../Generic/Check Path Lengths.asset.meta | 8 + .../Check Prefab Transforms.asset.meta | 8 + .../Check Script Compilation.asset.meta | 8 + .../Check Shader Compilation.asset.meta | 8 + .../Check Texture Dimensions.asset.meta | 8 + .../Generic/Check Type Namespaces.asset.meta | 8 + .../Remove Executable Files.asset.meta | 8 + .../Tests/Generic/Remove JPG Files.asset.meta | 8 + .../Remove JavaScript Files.asset.meta | 8 + .../Remove Lossy Audio Files.asset.meta | 8 + .../Generic/Remove Mixamo Files.asset.meta | 8 + .../Generic/Remove SpeedTree Files.asset.meta | 8 + .../Generic/Remove Video Files.asset.meta | 8 + .../Editor/Validator/Tests/UnityPackage.meta | 8 + .../UnityPackage/Check Demo Scenes.asset.meta | 8 + .../Check Documentation.asset.meta | 8 + .../Check Package Size.asset.meta | 8 + .../Check Project Template Assets.asset.meta | 8 + .../com.unity.asset-store-tools/LICENSE.md | 5 + .../LICENSE.md.meta | 7 + .../com.unity.asset-store-tools/package.json | 11 + .../package.json.meta | 7 + .../AssetStoreUploads/Packages/manifest.json | 45 ++ .../Packages/packages-lock.json | 417 +++++++++++ .../BurstAotSettings_StandaloneWindows.json | 17 + .../CommonBurstAotSettings.json | 6 + .../ProjectSettings/ProjectVersion.txt | 2 + .../SceneTemplateSettings.json | 167 +++++ docs/images/building_scene.gif | Bin 0 -> 2222269 bytes tools/prepare_unity_asset_store_release.py | 168 +++++ 775 files changed, 31969 insertions(+), 205 deletions(-) create mode 100644 TestProjects/AssetStoreUploads/.gitignore rename TestProjects/{UnityMCPTests/Assets/Scripts/TestScriptableObjectInstance.asset.meta => AssetStoreUploads/Assets/Readme.asset.meta} (78%) create mode 100644 TestProjects/AssetStoreUploads/Assets/Scenes.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity create mode 100644 TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/SampleSceneProfile.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced-Renderer.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity-Renderer.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant-Renderer.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons/URP.png create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons/URP.png.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Layout.wlt create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Layout.wlt.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts/Editor.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts/Editor/ReadmeEditor.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts/Readme.cs create mode 100644 TestProjects/AssetStoreUploads/Assets/TutorialInfo/Scripts/Readme.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Assets/UniversalRenderPipelineGlobalSettings.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/CHANGELOG.md create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/CHANGELOG.md.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/AuthenticationBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/AuthenticationBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAssetStoreApi.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAssetStoreApi.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAssetStoreClient.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAssetStoreClient.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAuthenticationType.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IAuthenticationType.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IPackageUploader.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/IPackageUploader.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/PackageUploaderBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Abstractions/PackageUploaderBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/ApiUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/ApiUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/AssetStoreApi.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/AssetStoreApi.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/AssetStoreClient.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/AssetStoreClient.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/CloudTokenAuthentication.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/CloudTokenAuthentication.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/CredentialsAuthentication.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/CredentialsAuthentication.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/Category.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/Category.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/Package.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/Package.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/PackageAdditionalData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/PackageAdditionalData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/User.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Models/User.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AssetStoreResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AssetStoreResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AssetStoreToolsVersionResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AssetStoreToolsVersionResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AuthenticationResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/AuthenticationResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/CategoryDataResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/CategoryDataResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackageThumbnailResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackageThumbnailResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackageUploadedUnityVersionDataResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackageUploadedUnityVersionDataResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackagesAdditionalDataResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackagesAdditionalDataResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackagesDataResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/PackagesDataResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/RefreshedPackageDataResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/RefreshedPackageDataResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/UploadResponse.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/Responses/UploadResponse.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/SessionAuthentication.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/SessionAuthentication.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/UnityPackageUploader.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/UnityPackageUploader.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/UploadStatus.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Api/UploadStatus.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssemblyInfo.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssemblyInfo.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssetStoreTools.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssetStoreTools.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssetStoreToolsWindow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/AssetStoreToolsWindow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Constants.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Constants.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/IPackageExporter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/IPackageExporter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/IPreviewInjector.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/IPreviewInjector.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/PackageExporterBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/PackageExporterBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/PackageExporterSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/Abstractions/PackageExporterSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/DefaultExporterSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/DefaultExporterSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/DefaultPackageExporter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/DefaultPackageExporter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/LegacyExporterSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/LegacyExporterSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/LegacyPackageExporter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/LegacyPackageExporter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterResult.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/PackageExporterResult.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/PreviewInjector.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Exporter/PreviewInjector.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/CustomPreviewGenerationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/CustomPreviewGenerationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/FileNameFormat.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/FileNameFormat.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/GenerationType.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/GenerationType.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/NativePreviewGenerationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/NativePreviewGenerationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewDatabase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewDatabase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewFormat.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewFormat.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewGenerationResult.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewGenerationResult.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewGenerationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewGenerationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewMetadata.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Data/PreviewMetadata.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/AudioChannel.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/AudioChannel.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/AudioChannelCoordinate.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/AudioChannelCoordinate.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/ISceneScreenshotter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/ISceneScreenshotter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/MaterialScreenshotter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/MaterialScreenshotter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/MeshScreenshotter.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/MeshScreenshotter.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/SceneScreenshotterBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/SceneScreenshotterBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/SceneScreenshotterSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/Screenshotters/SceneScreenshotterSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/AudioTypeGeneratorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/AudioTypeGeneratorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/AudioTypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/AudioTypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/ITypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/ITypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/MaterialTypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/MaterialTypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/ModelTypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/ModelTypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/PrefabTypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/PrefabTypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TextureTypeGeneratorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TextureTypeGeneratorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TextureTypePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TextureTypePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypeGeneratorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypeGeneratorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorFromScene.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorFromScene.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorFromSceneSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/Custom/TypeGenerators/TypePreviewGeneratorFromSceneSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/CustomPreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/CustomPreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/IPreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/IPreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/NativePreviewGenerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/NativePreviewGenerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/PreviewGeneratorBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Generators/PreviewGeneratorBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/Caching.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/Caching/CachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/Caching/CachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/Caching/ICachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/Caching/ICachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/IPreviewService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/IPreviewService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/PreviewServiceProvider.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Services/PreviewServiceProvider.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/AssetPreview.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/AssetPreview.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/AssetPreviewCollection.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/AssetPreviewCollection.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IAssetPreview.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IAssetPreview.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IAssetPreviewCollection.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IAssetPreviewCollection.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IPreviewGeneratorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/IPreviewGeneratorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/PreviewGeneratorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Data/PreviewGeneratorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/AssetPreviewElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/AssetPreviewElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/GridListElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/GridListElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewCollectionElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewCollectionElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGenerateButtonElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGenerateButtonElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGeneratorPathsElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGeneratorPathsElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGeneratorSettingsElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewGeneratorSettingsElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewWindowDescriptionElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Elements/PreviewWindowDescriptionElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/PreviewGeneratorWindow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/PreviewGeneratorWindow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Views.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Views/PreviewListView.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/UI/Views/PreviewListView.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/GraphicsUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/GraphicsUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/PreviewConvertUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/PreviewConvertUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/PreviewSceneUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/PreviewSceneUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/RenderPipeline.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/RenderPipeline.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/RenderPipelineUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Scripts/Utility/RenderPipelineUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Previews/Styles/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Unity.AssetStoreTools.Editor.asmdef create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Unity.AssetStoreTools.Editor.asmdef.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/account-dark.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/account-dark.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/account-light.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/account-light.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/open-in-browser.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/open-in-browser.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher-portal-dark.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher-portal-dark.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher-portal-light.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Icons/publisher-portal-light.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackage.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackage.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackageContent.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackageContent.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackageGroup.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IPackageGroup.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IWorkflow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IWorkflow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IWorkflowServices.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/IWorkflowServices.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/WorkflowBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Abstractions/WorkflowBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/AssetsWorkflow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/AssetsWorkflow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/HybridPackageWorkflow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/HybridPackageWorkflow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Package.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Package.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageContent.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageContent.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageGroup.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageGroup.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageSorting.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/PackageSorting.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/AssetPath.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/AssetPath.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/AssetsWorkflowStateData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/AssetsWorkflowStateData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/HybridPackageWorkflowState.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/HybridPackageWorkflowState.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/UnityPackageWorkflowStateData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/UnityPackageWorkflowStateData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/WorkflowStateData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/Serialization/WorkflowStateData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/UnityPackageWorkflow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/UnityPackageWorkflow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/WorkflowServices.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Data/WorkflowServices.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/AnalyticsService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/AnalyticsService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/AuthenticationAnalytic.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/AuthenticationAnalytic.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/BaseAnalytic.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/BaseAnalytic.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/IAssetStoreAnalytic.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/IAssetStoreAnalytic.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/IAssetStoreAnalyticData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/IAssetStoreAnalyticData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/PackageUploadAnalytic.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/PackageUploadAnalytic.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/ValidationResultsSerializer.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/Data/ValidationResultsSerializer.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/IAnalyticsService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Analytics/IAnalyticsService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/AuthenticationService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/AuthenticationService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IAuthenticationService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IAuthenticationService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IPackageDownloadingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IPackageDownloadingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IPackageUploadingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/IPackageUploadingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/PackageDownloadingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/PackageDownloadingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/PackageUploadingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Api/PackageUploadingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Caching.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Caching/CachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Caching/CachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Caching/ICachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/Caching/ICachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/IUploaderService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/IUploaderService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/PackageFactory.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/PackageFactory/IPackageFactoryService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/PackageFactory/IPackageFactoryService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/PackageFactory/PackageFactoryService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/PackageFactory/PackageFactoryService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/UploaderServiceProvider.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/Services/UploaderServiceProvider.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/Abstractions/ValidationElementBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/Abstractions/ValidationElementBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/Abstractions/WorkflowElementBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/Abstractions/WorkflowElementBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/AccountToolbar.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/AccountToolbar.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/AssetsWorkflowElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/AssetsWorkflowElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/CurrentProjectValidationElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/CurrentProjectValidationElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/ExternalProjectValidationElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/ExternalProjectValidationElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/HybridPackageWorkflowElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/HybridPackageWorkflowElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/LoadingSpinner.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/LoadingSpinner.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/MultiToggleSelectionElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/MultiToggleSelectionElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageContentElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageContentElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageGroupElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageGroupElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageListToolbar.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageListToolbar.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageUploadElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PackageUploadElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PathSelectionElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PathSelectionElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PreviewGenerationElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/PreviewGenerationElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/UnityPackageWorkflowElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Elements/UnityPackageWorkflowElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Views.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Views/LoginView.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Views/LoginView.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Views/PackageListView.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Scripts/UI/Views/PackageListView.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/LoginView/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/PackageListView/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/Styles/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/UploaderWindow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Uploader/UploaderWindow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASDebug.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASDebug.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error_d.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error_d.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined_d.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined_d.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning_d.png create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning_d.png.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/CategoryEvaluator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/CategoryEvaluator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/ValidatorCategory.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/ValidatorCategory.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Animation Clips.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Audio Clipping.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Colliders.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Compressed Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Empty Prefabs.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check File Menu Names.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check LODs.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Line Endings.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Mesh Prefabs.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Assets.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Scenes.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Import Logs.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Orientation.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Types.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Normal Map Textures.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Package Naming.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Particle Systems.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Path Lengths.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Prefab Transforms.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Script Compilation.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Shader Compilation.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Texture Dimensions.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Type Namespaces.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Executable Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JPG Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JavaScript Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Lossy Audio Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Mixamo Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove SpeedTree Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Video Files.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Demo Scenes.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Documentation.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Package Size.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Project Template Assets.asset.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json create mode 100644 TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json.meta create mode 100644 TestProjects/AssetStoreUploads/Packages/manifest.json create mode 100644 TestProjects/AssetStoreUploads/Packages/packages-lock.json create mode 100644 TestProjects/AssetStoreUploads/ProjectSettings/BurstAotSettings_StandaloneWindows.json create mode 100644 TestProjects/AssetStoreUploads/ProjectSettings/CommonBurstAotSettings.json create mode 100644 TestProjects/AssetStoreUploads/ProjectSettings/ProjectVersion.txt create mode 100644 TestProjects/AssetStoreUploads/ProjectSettings/SceneTemplateSettings.json create mode 100644 docs/images/building_scene.gif create mode 100644 tools/prepare_unity_asset_store_release.py diff --git a/MCPForUnity/Editor/Clients/McpClientRegistry.cs b/MCPForUnity/Editor/Clients/McpClientRegistry.cs index b29c2f8..57e4dc1 100644 --- a/MCPForUnity/Editor/Clients/McpClientRegistry.cs +++ b/MCPForUnity/Editor/Clients/McpClientRegistry.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MCPForUnity.Editor.Helpers; using UnityEditor; using UnityEngine; @@ -47,7 +48,7 @@ namespace MCPForUnity.Editor.Clients } catch (Exception ex) { - Debug.LogWarning($"UnityMCP: Failed to instantiate configurator {type.Name}: {ex.Message}"); + McpLog.Warn($"UnityMCP: Failed to instantiate configurator {type.Name}: {ex.Message}"); } } diff --git a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs index bce2a82..0a4fbc2 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs @@ -152,7 +152,7 @@ namespace MCPForUnity.Editor.Helpers // Consider using by_name search with includeInactive if you need to find inactive objects. if (includeInactive) { - Debug.LogWarning("[GameObjectLookup] SearchByPath with includeInactive=true: " + + McpLog.Warn("[GameObjectLookup] SearchByPath with includeInactive=true: " + "GameObject.Find() cannot find inactive objects. Use by_name search instead."); } @@ -224,7 +224,7 @@ namespace MCPForUnity.Editor.Helpers Type componentType = FindComponentType(componentTypeName); if (componentType == null) { - Debug.LogWarning($"[GameObjectLookup] Component type '{componentTypeName}' not found."); + McpLog.Warn($"[GameObjectLookup] Component type '{componentTypeName}' not found."); yield break; } diff --git a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs index 538aa76..49e8ee8 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs @@ -122,7 +122,7 @@ namespace MCPForUnity.Editor.Helpers public static object GetComponentData(Component c, bool includeNonPublicSerializedFields = true) { // --- Add Early Logging --- - // Debug.Log($"[GetComponentData] Starting for component: {c?.GetType()?.FullName ?? "null"} (ID: {c?.GetInstanceID() ?? 0})"); + // McpLog.Info($"[GetComponentData] Starting for component: {c?.GetType()?.FullName ?? "null"} (ID: {c?.GetInstanceID() ?? 0})"); // --- End Early Logging --- if (c == null) return null; @@ -132,7 +132,7 @@ namespace MCPForUnity.Editor.Helpers if (componentType == typeof(Transform)) { Transform tr = c as Transform; - // Debug.Log($"[GetComponentData] Manually serializing Transform (ID: {tr.GetInstanceID()})"); + // McpLog.Info($"[GetComponentData] Manually serializing Transform (ID: {tr.GetInstanceID()})"); return new Dictionary { { "typeName", componentType.FullName }, @@ -295,7 +295,7 @@ namespace MCPForUnity.Editor.Helpers var serializablePropertiesOutput = new Dictionary(); // --- Add Logging Before Property Loop --- - // Debug.Log($"[GetComponentData] Starting property loop for {componentType.Name}..."); + // McpLog.Info($"[GetComponentData] Starting property loop for {componentType.Name}..."); // --- End Logging Before Property Loop --- // Use cached properties @@ -313,7 +313,7 @@ namespace MCPForUnity.Editor.Helpers // Also skip potentially problematic Matrix properties prone to cycles/errors propName == "worldToLocalMatrix" || propName == "localToWorldMatrix") { - // Debug.Log($"[GetComponentData] Explicitly skipping generic property: {propName}"); // Optional log + // McpLog.Info($"[GetComponentData] Explicitly skipping generic property: {propName}"); // Optional log skipProperty = true; } // --- End Skip Generic Properties --- @@ -330,7 +330,7 @@ namespace MCPForUnity.Editor.Helpers propName == "previousViewProjectionMatrix" || propName == "cameraToWorldMatrix")) { - // Debug.Log($"[GetComponentData] Explicitly skipping Camera property: {propName}"); + // McpLog.Info($"[GetComponentData] Explicitly skipping Camera property: {propName}"); skipProperty = true; } // --- End Skip Camera Properties --- @@ -342,7 +342,7 @@ namespace MCPForUnity.Editor.Helpers propName == "worldToLocalMatrix" || propName == "localToWorldMatrix")) { - // Debug.Log($"[GetComponentData] Explicitly skipping Transform property: {propName}"); + // McpLog.Info($"[GetComponentData] Explicitly skipping Transform property: {propName}"); skipProperty = true; } // --- End Skip Transform Properties --- @@ -356,7 +356,7 @@ namespace MCPForUnity.Editor.Helpers try { // --- Add detailed logging --- - // Debug.Log($"[GetComponentData] Accessing: {componentType.Name}.{propName}"); + // McpLog.Info($"[GetComponentData] Accessing: {componentType.Name}.{propName}"); // --- End detailed logging --- // --- Special handling for material/mesh properties in edit mode --- @@ -392,12 +392,12 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception) { - // Debug.LogWarning($"Could not read property {propName} on {componentType.Name}"); + // McpLog.Warn($"Could not read property {propName} on {componentType.Name}"); } } // --- Add Logging Before Field Loop --- - // Debug.Log($"[GetComponentData] Starting field loop for {componentType.Name}..."); + // McpLog.Info($"[GetComponentData] Starting field loop for {componentType.Name}..."); // --- End Logging Before Field Loop --- // Use cached fields @@ -406,7 +406,7 @@ namespace MCPForUnity.Editor.Helpers try { // --- Add detailed logging for fields --- - // Debug.Log($"[GetComponentData] Accessing Field: {componentType.Name}.{fieldInfo.Name}"); + // McpLog.Info($"[GetComponentData] Accessing Field: {componentType.Name}.{fieldInfo.Name}"); // --- End detailed logging for fields --- object value = fieldInfo.GetValue(c); string fieldName = fieldInfo.Name; @@ -415,7 +415,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception) { - // Debug.LogWarning($"Could not read field {fieldInfo.Name} on {componentType.Name}"); + // McpLog.Warn($"Could not read field {fieldInfo.Name} on {componentType.Name}"); } } // --- End Use cached metadata --- @@ -452,7 +452,7 @@ namespace MCPForUnity.Editor.Helpers catch (Exception e) { // Catch potential errors during JToken conversion or addition to dictionary - Debug.LogWarning($"[AddSerializableValue] Error processing value for '{name}' (Type: {type.FullName}): {e.Message}. Skipping."); + McpLog.Warn($"[AddSerializableValue] Error processing value for '{name}' (Type: {type.FullName}): {e.Message}. Skipping."); } } @@ -508,7 +508,7 @@ namespace MCPForUnity.Editor.Helpers { return jValue.Value; } - // Debug.LogWarning($"Unsupported JTokenType encountered: {token.Type}. Returning null."); + // McpLog.Warn($"Unsupported JTokenType encountered: {token.Type}. Returning null."); return null; } } @@ -545,12 +545,12 @@ namespace MCPForUnity.Editor.Helpers } catch (JsonSerializationException e) { - Debug.LogWarning($"[GameObjectSerializer] Newtonsoft.Json Error serializing value of type {type.FullName}: {e.Message}. Skipping property/field."); + McpLog.Warn($"[GameObjectSerializer] Newtonsoft.Json Error serializing value of type {type.FullName}: {e.Message}. Skipping property/field."); return null; // Indicate serialization failure } catch (Exception e) // Catch other unexpected errors { - Debug.LogWarning($"[GameObjectSerializer] Unexpected error serializing value of type {type.FullName}: {e}. Skipping property/field."); + McpLog.Warn($"[GameObjectSerializer] Unexpected error serializing value of type {type.FullName}: {e}. Skipping property/field."); return null; // Indicate serialization failure } } diff --git a/MCPForUnity/Editor/Helpers/MaterialOps.cs b/MCPForUnity/Editor/Helpers/MaterialOps.cs index 0f80fcb..19f5028 100644 --- a/MCPForUnity/Editor/Helpers/MaterialOps.cs +++ b/MCPForUnity/Editor/Helpers/MaterialOps.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using MCPForUnity.Editor.Tools; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using UnityEngine; using UnityEditor; -using MCPForUnity.Editor.Tools; +using UnityEngine; namespace MCPForUnity.Editor.Helpers { @@ -63,7 +63,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[MaterialOps] Failed to parse color for property '{propName}': {ex.Message}"); + McpLog.Warn($"[MaterialOps] Failed to parse color for property '{propName}': {ex.Message}"); } } } @@ -81,7 +81,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[MaterialOps] Failed to parse color array: {ex.Message}"); + McpLog.Warn($"[MaterialOps] Failed to parse color array: {ex.Message}"); } } @@ -104,7 +104,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[MaterialOps] Failed to set float property '{propName}': {ex.Message}"); + McpLog.Warn($"[MaterialOps] Failed to set float property '{propName}': {ex.Message}"); } } } @@ -123,7 +123,7 @@ namespace MCPForUnity.Editor.Helpers // Use ResolvePropertyName to handle aliases even for structured texture names string candidateName = string.IsNullOrEmpty(rawName) ? "_BaseMap" : rawName; string targetProp = ResolvePropertyName(mat, candidateName); - + if (!string.IsNullOrEmpty(targetProp) && mat.HasProperty(targetProp)) { if (mat.GetTexture(targetProp) != newTex) @@ -231,17 +231,17 @@ namespace MCPForUnity.Editor.Helpers { if (material.HasProperty(propertyName)) { - try { material.SetColor(propertyName, ParseColor(value, serializer)); return true; } - catch (Exception ex) - { + try { material.SetColor(propertyName, ParseColor(value, serializer)); return true; } + catch (Exception ex) + { // Log at Debug level since we'll try other conversions - Debug.Log($"[MaterialOps] SetColor attempt for '{propertyName}' failed: {ex.Message}"); + McpLog.Info($"[MaterialOps] SetColor attempt for '{propertyName}' failed: {ex.Message}"); } - try { Vector4 vec = value.ToObject(serializer); material.SetVector(propertyName, vec); return true; } - catch (Exception ex) - { - Debug.Log($"[MaterialOps] SetVector (Vec4) attempt for '{propertyName}' failed: {ex.Message}"); + try { Vector4 vec = value.ToObject(serializer); material.SetVector(propertyName, vec); return true; } + catch (Exception ex) + { + McpLog.Info($"[MaterialOps] SetVector (Vec4) attempt for '{propertyName}' failed: {ex.Message}"); } } } @@ -249,10 +249,10 @@ namespace MCPForUnity.Editor.Helpers { if (material.HasProperty(propertyName)) { - try { material.SetColor(propertyName, ParseColor(value, serializer)); return true; } - catch (Exception ex) - { - Debug.Log($"[MaterialOps] SetColor (Vec3) attempt for '{propertyName}' failed: {ex.Message}"); + try { material.SetColor(propertyName, ParseColor(value, serializer)); return true; } + catch (Exception ex) + { + McpLog.Info($"[MaterialOps] SetColor (Vec3) attempt for '{propertyName}' failed: {ex.Message}"); } } } @@ -260,10 +260,10 @@ namespace MCPForUnity.Editor.Helpers { if (material.HasProperty(propertyName)) { - try { Vector2 vec = value.ToObject(serializer); material.SetVector(propertyName, vec); return true; } - catch (Exception ex) - { - Debug.Log($"[MaterialOps] SetVector (Vec2) attempt for '{propertyName}' failed: {ex.Message}"); + try { Vector2 vec = value.ToObject(serializer); material.SetVector(propertyName, vec); return true; } + catch (Exception ex) + { + McpLog.Info($"[MaterialOps] SetVector (Vec2) attempt for '{propertyName}' failed: {ex.Message}"); } } } @@ -273,10 +273,10 @@ namespace MCPForUnity.Editor.Helpers if (!material.HasProperty(propertyName)) return false; - try { material.SetFloat(propertyName, value.ToObject(serializer)); return true; } + try { material.SetFloat(propertyName, value.ToObject(serializer)); return true; } catch (Exception ex) { - Debug.Log($"[MaterialOps] SetFloat attempt for '{propertyName}' failed: {ex.Message}"); + McpLog.Info($"[MaterialOps] SetFloat attempt for '{propertyName}' failed: {ex.Message}"); } } else if (value.Type == JTokenType.Boolean) @@ -284,10 +284,10 @@ namespace MCPForUnity.Editor.Helpers if (!material.HasProperty(propertyName)) return false; - try { material.SetFloat(propertyName, value.ToObject(serializer) ? 1f : 0f); return true; } + try { material.SetFloat(propertyName, value.ToObject(serializer) ? 1f : 0f); return true; } catch (Exception ex) { - Debug.Log($"[MaterialOps] SetFloat (bool) attempt for '{propertyName}' failed: {ex.Message}"); + McpLog.Info($"[MaterialOps] SetFloat (bool) attempt for '{propertyName}' failed: {ex.Message}"); } } else if (value.Type == JTokenType.String) @@ -298,16 +298,16 @@ namespace MCPForUnity.Editor.Helpers string path = value.ToString(); if (!string.IsNullOrEmpty(path) && path.Contains("/")) // Heuristic: paths usually have slashes { - // We need to handle texture assignment here. - // Since we don't have easy access to AssetDatabase here directly without using UnityEditor namespace (which is imported), - // we can try to load it. - var sanitizedPath = AssetPathUtility.SanitizeAssetPath(path); - Texture tex = AssetDatabase.LoadAssetAtPath(sanitizedPath); - if (tex != null && material.HasProperty(propertyName)) - { - material.SetTexture(propertyName, tex); - return true; - } + // We need to handle texture assignment here. + // Since we don't have easy access to AssetDatabase here directly without using UnityEditor namespace (which is imported), + // we can try to load it. + var sanitizedPath = AssetPathUtility.SanitizeAssetPath(path); + Texture tex = AssetDatabase.LoadAssetAtPath(sanitizedPath); + if (tex != null && material.HasProperty(propertyName)) + { + material.SetTexture(propertyName, tex); + return true; + } } } catch (Exception ex) @@ -315,10 +315,10 @@ namespace MCPForUnity.Editor.Helpers McpLog.Warn($"SetTexture (string path) for '{propertyName}' failed: {ex.Message}"); } } - + if (value.Type == JTokenType.Object) { - try + try { Texture texture = value.ToObject(serializer); if (texture != null && material.HasProperty(propertyName)) @@ -333,7 +333,7 @@ namespace MCPForUnity.Editor.Helpers } } - Debug.LogWarning( + McpLog.Warn( $"[MaterialOps] Unsupported or failed conversion for material property '{propertyName}' from value: {value.ToString(Formatting.None)}" ); return false; @@ -382,14 +382,14 @@ namespace MCPForUnity.Editor.Helpers throw new ArgumentException("Color array must have 3 or 4 elements."); } } - + try { return token.ToObject(serializer); } catch (Exception ex) { - Debug.LogWarning($"[MaterialOps] Failed to parse color from token: {ex.Message}"); + McpLog.Warn($"[MaterialOps] Failed to parse color from token: {ex.Message}"); throw; } } diff --git a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs index 9ead6f2..61eccb1 100644 --- a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs +++ b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs @@ -49,7 +49,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception e) { - Debug.LogWarning($"Error reading existing config: {e.Message}."); + McpLog.Warn($"Error reading existing config: {e.Message}."); } } @@ -71,7 +71,7 @@ namespace MCPForUnity.Editor.Helpers // If user has partial/invalid JSON (e.g., mid-edit), start from a fresh object if (!string.IsNullOrWhiteSpace(existingJson)) { - Debug.LogWarning("UnityMCP: Configuration file could not be parsed; rewriting server block."); + McpLog.Warn("UnityMCP: Configuration file could not be parsed; rewriting server block."); } existingConfig = new JObject(); } @@ -137,7 +137,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception e) { - Debug.LogWarning($"UnityMCP: Failed to read Codex config '{configPath}': {e.Message}"); + McpLog.Warn($"UnityMCP: Failed to read Codex config '{configPath}': {e.Message}"); existingToml = string.Empty; } } diff --git a/MCPForUnity/Editor/Helpers/ObjectResolver.cs b/MCPForUnity/Editor/Helpers/ObjectResolver.cs index 6fc6065..ee35170 100644 --- a/MCPForUnity/Editor/Helpers/ObjectResolver.cs +++ b/MCPForUnity/Editor/Helpers/ObjectResolver.cs @@ -1,4 +1,5 @@ using System; +using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; @@ -39,7 +40,7 @@ namespace MCPForUnity.Editor.Helpers if (string.IsNullOrEmpty(findTerm)) { - Debug.LogWarning("[ObjectResolver] Find instruction missing 'find' term."); + McpLog.Warn("[ObjectResolver] Find instruction missing 'find' term."); return null; } @@ -85,20 +86,20 @@ namespace MCPForUnity.Editor.Helpers } else { - Debug.LogWarning($"[ObjectResolver] Could not find component type '{componentName}'. Falling back to target type '{targetType.Name}'."); + McpLog.Warn($"[ObjectResolver] Could not find component type '{componentName}'. Falling back to target type '{targetType.Name}'."); } } Component foundComp = foundGo.GetComponent(componentToGetType); if (foundComp == null) { - Debug.LogWarning($"[ObjectResolver] Found GameObject '{foundGo.name}' but could not find component of type '{componentToGetType.Name}'."); + McpLog.Warn($"[ObjectResolver] Found GameObject '{foundGo.name}' but could not find component of type '{componentToGetType.Name}'."); } return foundComp; } else { - Debug.LogWarning($"[ObjectResolver] Find instruction handling not implemented for target type: {targetType.Name}"); + McpLog.Warn($"[ObjectResolver] Find instruction handling not implemented for target type: {targetType.Name}"); return null; } } @@ -190,7 +191,7 @@ namespace MCPForUnity.Editor.Helpers } else if (guids.Length > 1) { - Debug.LogWarning($"[ObjectResolver] Ambiguous asset find: Found {guids.Length} assets matching filter '{searchFilter}'. Provide a full path or unique name."); + McpLog.Warn($"[ObjectResolver] Ambiguous asset find: Found {guids.Length} assets matching filter '{searchFilter}'. Provide a full path or unique name."); return null; } diff --git a/MCPForUnity/Editor/Helpers/PropertyConversion.cs b/MCPForUnity/Editor/Helpers/PropertyConversion.cs index a80ec27..0e3af0a 100644 --- a/MCPForUnity/Editor/Helpers/PropertyConversion.cs +++ b/MCPForUnity/Editor/Helpers/PropertyConversion.cs @@ -1,6 +1,7 @@ using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using MCPForUnity.Editor.Helpers; using UnityEditor; using UnityEngine; @@ -24,7 +25,7 @@ namespace MCPForUnity.Editor.Helpers { if (targetType.IsValueType && Nullable.GetUnderlyingType(targetType) == null) { - Debug.LogWarning($"[PropertyConversion] Cannot assign null to non-nullable value type {targetType.Name}. Returning default value."); + McpLog.Warn($"[PropertyConversion] Cannot assign null to non-nullable value type {targetType.Name}. Returning default value."); return Activator.CreateInstance(targetType); } return null; @@ -37,7 +38,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogError($"Error converting token to {targetType.FullName}: {ex.Message}\nToken: {token.ToString(Formatting.None)}"); + McpLog.Error($"Error converting token to {targetType.FullName}: {ex.Message}\nToken: {token.ToString(Formatting.None)}"); throw; } } @@ -82,7 +83,7 @@ namespace MCPForUnity.Editor.Helpers if (loadedAsset == null) { - Debug.LogWarning($"[PropertyConversion] Could not load asset of type {targetType.Name} from path: {assetPath}"); + McpLog.Warn($"[PropertyConversion] Could not load asset of type {targetType.Name} from path: {assetPath}"); } return loadedAsset; diff --git a/MCPForUnity/Editor/Helpers/VectorParsing.cs b/MCPForUnity/Editor/Helpers/VectorParsing.cs index 60bb7e8..a2e0a6c 100644 --- a/MCPForUnity/Editor/Helpers/VectorParsing.cs +++ b/MCPForUnity/Editor/Helpers/VectorParsing.cs @@ -44,7 +44,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Vector3 from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Vector3 from '{token}': {ex.Message}"); } return null; @@ -90,7 +90,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Vector2 from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Vector2 from '{token}': {ex.Message}"); } return null; @@ -162,7 +162,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Quaternion from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Quaternion from '{token}': {ex.Message}"); } return null; @@ -218,7 +218,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Color from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Color from '{token}': {ex.Message}"); } return null; @@ -260,7 +260,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Rect from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Rect from '{token}': {ex.Message}"); } return null; @@ -286,7 +286,7 @@ namespace MCPForUnity.Editor.Helpers } catch (Exception ex) { - Debug.LogWarning($"[VectorParsing] Failed to parse Bounds from '{token}': {ex.Message}"); + McpLog.Warn($"[VectorParsing] Failed to parse Bounds from '{token}': {ex.Message}"); } return null; diff --git a/MCPForUnity/Editor/Resources/Editor/Windows.cs b/MCPForUnity/Editor/Resources/Editor/Windows.cs index 5719088..b69a9d3 100644 --- a/MCPForUnity/Editor/Resources/Editor/Windows.cs +++ b/MCPForUnity/Editor/Resources/Editor/Windows.cs @@ -44,7 +44,7 @@ namespace MCPForUnity.Editor.Resources.Editor } catch (Exception ex) { - Debug.LogWarning($"Could not get info for window {window.GetType().Name}: {ex.Message}"); + McpLog.Warn($"Could not get info for window {window.GetType().Name}: {ex.Message}"); } } diff --git a/MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs b/MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs index c509edc..c04e956 100644 --- a/MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs +++ b/MCPForUnity/Editor/Resources/Scene/GameObjectResource.cs @@ -58,7 +58,7 @@ namespace MCPForUnity.Editor.Resources.Scene } catch (Exception e) { - Debug.LogError($"[GameObjectResource] Error getting GameObject: {e}"); + McpLog.Error($"[GameObjectResource] Error getting GameObject: {e}"); return new ErrorResponse($"Error getting GameObject: {e.Message}"); } } @@ -199,7 +199,7 @@ namespace MCPForUnity.Editor.Resources.Scene } catch (Exception e) { - Debug.LogError($"[GameObjectComponentsResource] Error getting components: {e}"); + McpLog.Error($"[GameObjectComponentsResource] Error getting components: {e}"); return new ErrorResponse($"Error getting components: {e.Message}"); } } @@ -276,7 +276,7 @@ namespace MCPForUnity.Editor.Resources.Scene } catch (Exception e) { - Debug.LogError($"[GameObjectComponentResource] Error getting component: {e}"); + McpLog.Error($"[GameObjectComponentResource] Error getting component: {e}"); return new ErrorResponse($"Error getting component: {e.Message}"); } } diff --git a/MCPForUnity/Editor/Tools/FindGameObjects.cs b/MCPForUnity/Editor/Tools/FindGameObjects.cs index 3a59706..890865a 100644 --- a/MCPForUnity/Editor/Tools/FindGameObjects.cs +++ b/MCPForUnity/Editor/Tools/FindGameObjects.cs @@ -64,7 +64,7 @@ namespace MCPForUnity.Editor.Tools } catch (System.Exception ex) { - Debug.LogError($"[FindGameObjects] Error searching GameObjects: {ex.Message}"); + McpLog.Error($"[FindGameObjects] Error searching GameObjects: {ex.Message}"); return new ErrorResponse($"Error searching GameObjects: {ex.Message}"); } } diff --git a/MCPForUnity/Editor/Tools/JsonUtil.cs b/MCPForUnity/Editor/Tools/JsonUtil.cs index 00cbe3f..4225954 100644 --- a/MCPForUnity/Editor/Tools/JsonUtil.cs +++ b/MCPForUnity/Editor/Tools/JsonUtil.cs @@ -1,3 +1,4 @@ +using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEngine; @@ -22,7 +23,7 @@ namespace MCPForUnity.Editor.Tools } catch (Newtonsoft.Json.JsonReaderException e) { - Debug.LogWarning($"[MCP] Could not parse '{paramName}' JSON string: {e.Message}"); + McpLog.Warn($"[MCP] Could not parse '{paramName}' JSON string: {e.Message}"); } } } diff --git a/MCPForUnity/Editor/Tools/ManageAsset.cs b/MCPForUnity/Editor/Tools/ManageAsset.cs index 30daf5d..9cdf627 100644 --- a/MCPForUnity/Editor/Tools/ManageAsset.cs +++ b/MCPForUnity/Editor/Tools/ManageAsset.cs @@ -74,7 +74,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogWarning($"[ManageAsset] Could not parse 'properties' JSON string: {e.Message}"); + McpLog.Warn($"[ManageAsset] Could not parse 'properties' JSON string: {e.Message}"); } } @@ -119,7 +119,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ManageAsset] Action '{action}' failed for path '{path}': {e}"); + McpLog.Error($"[ManageAsset] Action '{action}' failed for path '{path}': {e}"); return new ErrorResponse( $"Internal error processing action '{action}' on '{path}': {e.Message}" ); @@ -143,7 +143,7 @@ namespace MCPForUnity.Editor.Tools // applying properties via reflection or specific methods, saving, then reimporting. if (properties != null && properties.HasValues) { - Debug.LogWarning( + McpLog.Warn( "[ManageAsset.Reimport] Modifying importer properties before reimport is not fully implemented yet." ); // AssetImporter importer = AssetImporter.GetAtPath(fullPath); @@ -376,7 +376,7 @@ namespace MCPForUnity.Editor.Tools // Only warn about resolution failure if component also not found if (targetComponent == null && !resolved) { - Debug.LogWarning( + McpLog.Warn( $"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}" ); } @@ -393,7 +393,7 @@ namespace MCPForUnity.Editor.Tools else { // Log a warning if a specified component couldn't be found - Debug.LogWarning( + McpLog.Warn( $"[ManageAsset.ModifyAsset] Component '{componentName}' not found on GameObject '{gameObject.name}' in asset '{fullPath}'. Skipping modification for this component." ); } @@ -403,7 +403,7 @@ namespace MCPForUnity.Editor.Tools // Log a warning if the structure isn't {"ComponentName": {"prop": value}} // We could potentially try to apply this property directly to the GameObject here if needed, // but the primary goal is component modification. - Debug.LogWarning( + McpLog.Warn( $"[ManageAsset.ModifyAsset] Property '{prop.Name}' for GameObject modification should have a JSON object value containing component properties. Value was: {prop.Value.Type}. Skipping." ); } @@ -444,7 +444,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"Could not get TextureImporter for {fullPath}."); + McpLog.Warn($"Could not get TextureImporter for {fullPath}."); } } // TODO: Add modification logic for other common asset types (Models, AudioClips importers, etc.) @@ -452,7 +452,7 @@ namespace MCPForUnity.Editor.Tools { // This block handles non-GameObject/Material/ScriptableObject/Texture assets. // Attempts to apply properties directly to the asset itself. - Debug.LogWarning( + McpLog.Warn( $"[ManageAsset.ModifyAsset] Asset type '{asset.GetType().Name}' at '{fullPath}' is not explicitly handled for component modification. Attempting generic property setting on the asset itself." ); modified |= ApplyObjectProperties(asset, properties); @@ -486,7 +486,7 @@ namespace MCPForUnity.Editor.Tools catch (Exception e) { // Log the detailed error internally - Debug.LogError($"[ManageAsset] Action 'modify' failed for path '{path}': {e}"); + McpLog.Error($"[ManageAsset] Action 'modify' failed for path '{path}': {e}"); // Return a user-friendly error message return new ErrorResponse($"Failed to modify asset '{fullPath}': {e.Message}"); } @@ -648,7 +648,7 @@ namespace MCPForUnity.Editor.Tools { // Maybe the user provided a file path instead of a folder? // We could search in the containing folder, or return an error. - Debug.LogWarning( + McpLog.Warn( $"Search path '{folderScope[0]}' is not a valid folder. Searching entire project." ); folderScope = null; // Search everywhere if path isn't a folder @@ -671,7 +671,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"Could not parse filterDateAfter: '{filterDateAfterStr}'. Expected ISO 8601 format." ); } @@ -816,7 +816,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError( + McpLog.Error( $"[ManageAsset.GetComponentsFromAsset] Error getting components for '{fullPath}': {e}" ); return new ErrorResponse( @@ -1018,7 +1018,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning( + McpLog.Warn( $"[SetPropertyOrField] Failed to set '{memberName}' on {type.Name}: {ex.Message}" ); } @@ -1081,7 +1081,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning( + McpLog.Warn( $"Failed to generate readable preview for '{path}': {ex.Message}. Preview might not be readable." ); // Fallback: Try getting static preview if available? @@ -1090,7 +1090,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"Could not get asset preview for {path} (Type: {assetType?.Name}). Is it supported?" ); } diff --git a/MCPForUnity/Editor/Tools/ManageComponents.cs b/MCPForUnity/Editor/Tools/ManageComponents.cs index 57ec128..7f5a8b0 100644 --- a/MCPForUnity/Editor/Tools/ManageComponents.cs +++ b/MCPForUnity/Editor/Tools/ManageComponents.cs @@ -58,7 +58,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ManageComponents] Action '{action}' failed: {e}"); + McpLog.Error($"[ManageComponents] Action '{action}' failed: {e}"); return new ErrorResponse($"Internal error processing action '{action}': {e.Message}"); } } @@ -303,7 +303,7 @@ namespace MCPForUnity.Editor.Tools if (errors.Count > 0) { - Debug.LogWarning($"[ManageComponents] Some properties failed to set on {component.GetType().Name}: {string.Join(", ", errors)}"); + McpLog.Warn($"[ManageComponents] Some properties failed to set on {component.GetType().Name}: {string.Join(", ", errors)}"); } } @@ -321,7 +321,7 @@ namespace MCPForUnity.Editor.Tools return null; // Success } - Debug.LogWarning($"[ManageComponents] {error}"); + McpLog.Warn($"[ManageComponents] {error}"); return error; } diff --git a/MCPForUnity/Editor/Tools/ManageGameObject.cs b/MCPForUnity/Editor/Tools/ManageGameObject.cs index 28d8feb..4a01007 100644 --- a/MCPForUnity/Editor/Tools/ManageGameObject.cs +++ b/MCPForUnity/Editor/Tools/ManageGameObject.cs @@ -72,7 +72,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogWarning($"[ManageGameObject] Could not parse 'componentProperties' JSON string: {e.Message}"); + McpLog.Warn($"[ManageGameObject] Could not parse 'componentProperties' JSON string: {e.Message}"); } } @@ -116,7 +116,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ManageGameObject] Action '{action}' failed: {e}"); + McpLog.Error($"[ManageGameObject] Action '{action}' failed: {e}"); return new ErrorResponse($"Internal error processing action '{action}': {e.Message}"); } } @@ -149,7 +149,7 @@ namespace MCPForUnity.Editor.Tools ) { string prefabNameOnly = prefabPath; - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] Searching for prefab named: '{prefabNameOnly}'" ); string[] guids = AssetDatabase.FindAssets($"t:Prefab {prefabNameOnly}"); @@ -172,7 +172,7 @@ namespace MCPForUnity.Editor.Tools else // Exactly one found { prefabPath = AssetDatabase.GUIDToAssetPath(guids[0]); // Update prefabPath with the full path - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] Found unique prefab at path: '{prefabPath}'" ); } @@ -180,7 +180,7 @@ namespace MCPForUnity.Editor.Tools else if (!prefabPath.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { // If it looks like a path but doesn't end with .prefab, assume user forgot it and append it. - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Create] Provided prefabPath '{prefabPath}' does not end with .prefab. Assuming it's missing and appending." ); prefabPath += ".prefab"; @@ -200,7 +200,7 @@ namespace MCPForUnity.Editor.Tools if (newGo == null) { // This might happen if the asset exists but isn't a valid GameObject prefab somehow - Debug.LogError( + McpLog.Error( $"[ManageGameObject.Create] Failed to instantiate prefab at '{prefabPath}', asset might be corrupted or not a GameObject." ); return new ErrorResponse( @@ -217,7 +217,7 @@ namespace MCPForUnity.Editor.Tools newGo, $"Instantiate Prefab '{prefabAsset.name}' as '{newGo.name}'" ); - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] Instantiated prefab '{prefabAsset.name}' from path '{prefabPath}' as '{newGo.name}'." ); } @@ -232,7 +232,7 @@ namespace MCPForUnity.Editor.Tools { // Only return error if prefabPath was specified but not found. // If prefabPath was empty/null, we proceed to create primitive/empty. - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Create] Prefab asset not found at path: '{prefabPath}'. Will proceed to create new object if specified." ); // Do not return error here, allow fallback to primitive/empty creation @@ -337,7 +337,7 @@ namespace MCPForUnity.Editor.Tools // Check if tag exists first (Unity doesn't throw exceptions for undefined tags, just logs a warning) if (tag != "Untagged" && !System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tag)) { - Debug.Log($"[ManageGameObject.Create] Tag '{tag}' not found. Creating it."); + McpLog.Info($"[ManageGameObject.Create] Tag '{tag}' not found. Creating it."); try { InternalEditorUtility.AddTag(tag); @@ -371,7 +371,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Create] Layer '{layerName}' not found. Using default layer." ); } @@ -406,7 +406,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject] Invalid component format in componentsToAdd: {compToken}" ); } @@ -430,7 +430,7 @@ namespace MCPForUnity.Editor.Tools // Ensure the *saving* path ends with .prefab if (!finalPrefabPath.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] Appending .prefab extension to save path: '{finalPrefabPath}' -> '{finalPrefabPath}.prefab'" ); finalPrefabPath += ".prefab"; @@ -447,7 +447,7 @@ namespace MCPForUnity.Editor.Tools { System.IO.Directory.CreateDirectory(directoryPath); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); // Refresh asset database to recognize the new folder - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] Created directory for prefab: {directoryPath}" ); } @@ -466,7 +466,7 @@ namespace MCPForUnity.Editor.Tools $"Failed to save GameObject '{name}' as prefab at '{finalPrefabPath}'. Check path and permissions." ); } - Debug.Log( + McpLog.Info( $"[ManageGameObject.Create] GameObject '{name}' saved as prefab to '{finalPrefabPath}' and instance connected." ); // Mark the new prefab asset as dirty? Not usually necessary, SaveAsPrefabAsset handles it. @@ -593,7 +593,7 @@ namespace MCPForUnity.Editor.Tools // Check if tag exists first (Unity doesn't throw exceptions for undefined tags, just logs a warning) if (tagToSet != "Untagged" && !System.Linq.Enumerable.Contains(InternalEditorUtility.tags, tagToSet)) { - Debug.Log($"[ManageGameObject] Tag '{tagToSet}' not found. Creating it."); + McpLog.Info($"[ManageGameObject] Tag '{tagToSet}' not found. Creating it."); try { InternalEditorUtility.AddTag(tagToSet); @@ -850,7 +850,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"[ManageGameObject.Duplicate] Parent '{parentToken}' not found. Keeping original parent."); + McpLog.Warn($"[ManageGameObject.Duplicate] Parent '{parentToken}' not found. Keeping original parent."); } } } @@ -976,7 +976,7 @@ namespace MCPForUnity.Editor.Tools case "forward": case "front": return Vector3.forward; case "back": case "backward": case "behind": return Vector3.back; default: - Debug.LogWarning($"[ManageGameObject.MoveRelative] Unknown direction '{direction}', defaulting to forward."); + McpLog.Warn($"[ManageGameObject.MoveRelative] Unknown direction '{direction}', defaulting to forward."); return Vector3.forward; } } @@ -992,7 +992,7 @@ namespace MCPForUnity.Editor.Tools case "forward": case "front": return referenceTransform.forward; case "back": case "backward": case "behind": return -referenceTransform.forward; default: - Debug.LogWarning($"[ManageGameObject.MoveRelative] Unknown direction '{direction}', defaulting to forward."); + McpLog.Warn($"[ManageGameObject.MoveRelative] Unknown direction '{direction}', defaulting to forward."); return referenceTransform.forward; } } @@ -1057,7 +1057,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning($"Failed to parse JArray as Vector3: {array}. Error: {ex.Message}"); + McpLog.Warn($"Failed to parse JArray as Vector3: {array}. Error: {ex.Message}"); } } return null; @@ -1124,7 +1124,7 @@ namespace MCPForUnity.Editor.Tools rootSearchObject = FindObjectInternal(targetToken, "by_id_or_name_or_path"); // Find the root for child search if (rootSearchObject == null) { - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Find] Root object '{targetToken}' for child search not found." ); return results; // Return empty if root not found @@ -1209,7 +1209,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Find] Component type not found: {searchTerm}" ); } @@ -1238,7 +1238,7 @@ namespace MCPForUnity.Editor.Tools results.AddRange(allObjectsName.Where(go => go.name == searchTerm)); break; default: - Debug.LogWarning( + McpLog.Warn( $"[ManageGameObject.Find] Unknown search method: {searchMethod}" ); break; @@ -1465,13 +1465,13 @@ namespace MCPForUnity.Editor.Tools var msg = suggestions.Any() ? $"Property '{propName}' not found. Did you mean: {string.Join(", ", suggestions)}? Available: [{string.Join(", ", availableProperties)}]" : $"Property '{propName}' not found. Available: [{string.Join(", ", availableProperties)}]"; - Debug.LogWarning($"[ManageGameObject] {msg}"); + McpLog.Warn($"[ManageGameObject] {msg}"); failures.Add(msg); } } catch (Exception e) { - Debug.LogError( + McpLog.Error( $"[ManageGameObject] Error setting property '{propName}' on '{compName}': {e.Message}" ); failures.Add($"Error setting '{propName}': {e.Message}"); @@ -1522,7 +1522,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"[SetProperty] Conversion failed for property '{memberName}' (Type: {propInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); + McpLog.Warn($"[SetProperty] Conversion failed for property '{memberName}' (Type: {propInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); } } else @@ -1541,7 +1541,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); + McpLog.Warn($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); } } else @@ -1563,7 +1563,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogError( + McpLog.Error( $"[SetProperty] Failed to set '{memberName}' on {type.Name}: {ex.Message}\nToken: {value.ToString(Formatting.None)}" ); } @@ -1622,7 +1622,7 @@ namespace MCPForUnity.Editor.Tools fieldInfo = currentType.GetField(part, flags); if (fieldInfo == null) { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Could not find property or field '{part}' on type '{currentType.Name}'" ); return false; @@ -1637,7 +1637,7 @@ namespace MCPForUnity.Editor.Tools //Need to stop if current property is null if (currentObject == null) { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Property '{part}' is null, cannot access nested properties." ); return false; @@ -1650,7 +1650,7 @@ namespace MCPForUnity.Editor.Tools var materials = currentObject as Material[]; if (arrayIndex < 0 || arrayIndex >= materials.Length) { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Material index {arrayIndex} out of range (0-{materials.Length - 1})" ); return false; @@ -1662,7 +1662,7 @@ namespace MCPForUnity.Editor.Tools var list = currentObject as System.Collections.IList; if (arrayIndex < 0 || arrayIndex >= list.Count) { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Index {arrayIndex} out of range (0-{list.Count - 1})" ); return false; @@ -1671,7 +1671,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Property '{part}' is not an array or list, cannot access by index." ); return false; @@ -1702,7 +1702,7 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"[SetNestedProperty] Final conversion failed for property '{finalPart}' (Type: {finalPropInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); + McpLog.Warn($"[SetNestedProperty] Final conversion failed for property '{finalPart}' (Type: {finalPropInfo.PropertyType.Name}) from token: {value.ToString(Formatting.None)}"); } } else @@ -1719,12 +1719,12 @@ namespace MCPForUnity.Editor.Tools } else { - Debug.LogWarning($"[SetNestedProperty] Final conversion failed for field '{finalPart}' (Type: {finalFieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); + McpLog.Warn($"[SetNestedProperty] Final conversion failed for field '{finalPart}' (Type: {finalFieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}"); } } else { - Debug.LogWarning( + McpLog.Warn( $"[SetNestedProperty] Could not find final writable property or field '{finalPart}' on type '{currentType.Name}'" ); } @@ -1732,7 +1732,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogError( + McpLog.Error( $"[SetNestedProperty] Error setting nested property '{path}': {ex.Message}\nToken: {value.ToString(Formatting.None)}" ); } @@ -1805,7 +1805,7 @@ namespace MCPForUnity.Editor.Tools { return new Vector3(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject()); } - Debug.LogWarning($"Could not parse JToken '{token}' as Vector3 using fallback. Returning Vector3.zero."); + McpLog.Warn($"Could not parse JToken '{token}' as Vector3 using fallback. Returning Vector3.zero."); return Vector3.zero; } @@ -1820,7 +1820,7 @@ namespace MCPForUnity.Editor.Tools { return new Vector2(arr[0].ToObject(), arr[1].ToObject()); } - Debug.LogWarning($"Could not parse JToken '{token}' as Vector2 using fallback. Returning Vector2.zero."); + McpLog.Warn($"Could not parse JToken '{token}' as Vector2 using fallback. Returning Vector2.zero."); return Vector2.zero; } private static Quaternion ParseJTokenToQuaternion(JToken token) @@ -1834,7 +1834,7 @@ namespace MCPForUnity.Editor.Tools { return new Quaternion(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } - Debug.LogWarning($"Could not parse JToken '{token}' as Quaternion using fallback. Returning Quaternion.identity."); + McpLog.Warn($"Could not parse JToken '{token}' as Quaternion using fallback. Returning Quaternion.identity."); return Quaternion.identity; } private static Color ParseJTokenToColor(JToken token) @@ -1848,7 +1848,7 @@ namespace MCPForUnity.Editor.Tools { return new Color(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } - Debug.LogWarning($"Could not parse JToken '{token}' as Color using fallback. Returning Color.white."); + McpLog.Warn($"Could not parse JToken '{token}' as Color using fallback. Returning Color.white."); return Color.white; } private static Rect ParseJTokenToRect(JToken token) @@ -1862,7 +1862,7 @@ namespace MCPForUnity.Editor.Tools { return new Rect(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject(), arr[3].ToObject()); } - Debug.LogWarning($"Could not parse JToken '{token}' as Rect using fallback. Returning Rect.zero."); + McpLog.Warn($"Could not parse JToken '{token}' as Rect using fallback. Returning Rect.zero."); return Rect.zero; } private static Bounds ParseJTokenToBounds(JToken token) @@ -1880,7 +1880,7 @@ namespace MCPForUnity.Editor.Tools // { // return new Bounds(new Vector3(arr[0].ToObject(), arr[1].ToObject(), arr[2].ToObject()), new Vector3(arr[3].ToObject(), arr[4].ToObject(), arr[5].ToObject())); // } - Debug.LogWarning($"Could not parse JToken '{token}' as Bounds using fallback. Returning new Bounds(Vector3.zero, Vector3.zero)."); + McpLog.Warn($"Could not parse JToken '{token}' as Bounds using fallback. Returning new Bounds(Vector3.zero, Vector3.zero)."); return new Bounds(Vector3.zero, Vector3.zero); } // --- End Redundant Parse Helpers --- @@ -1913,7 +1913,7 @@ namespace MCPForUnity.Editor.Tools // Log the resolver error if type wasn't found if (!string.IsNullOrEmpty(error)) { - Debug.LogWarning($"[FindType] {error}"); + McpLog.Warn($"[FindType] {error}"); } return null; @@ -1980,7 +1980,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning($"[Property Matching] Error getting suggestions for '{userInput}': {ex.Message}"); + McpLog.Warn($"[Property Matching] Error getting suggestions for '{userInput}': {ex.Message}"); return new List(); } } diff --git a/MCPForUnity/Editor/Tools/ManageScript.cs b/MCPForUnity/Editor/Tools/ManageScript.cs index e1ae813..e86890f 100644 --- a/MCPForUnity/Editor/Tools/ManageScript.cs +++ b/MCPForUnity/Editor/Tools/ManageScript.cs @@ -263,7 +263,7 @@ namespace MCPForUnity.Editor.Tools : new ErrorResponse("Validation failed.", result); } case "edit": - Debug.LogWarning("manage_script.edit is deprecated; prefer apply_text_edits. Serving structured edit for backward compatibility."); + McpLog.Warn("manage_script.edit is deprecated; prefer apply_text_edits. Serving structured edit for backward compatibility."); var structEdits = @params["edits"] as JArray; var options = @params["options"] as JObject; return EditScript(fullPath, relativePath, name, structEdits, options); @@ -353,7 +353,7 @@ namespace MCPForUnity.Editor.Tools else if (validationErrors != null && validationErrors.Length > 0) { // Log warnings but don't block creation - Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors)); + McpLog.Warn($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors)); } try @@ -451,7 +451,7 @@ namespace MCPForUnity.Editor.Tools else if (validationErrors != null && validationErrors.Length > 0) { // Log warnings but don't block update - Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors)); + McpLog.Warn($"Script validation warnings for {name}:\n" + string.Join("\n", validationErrors)); } try @@ -1405,7 +1405,7 @@ namespace MCPForUnity.Editor.Tools if (!ValidateScriptSyntax(working, level, out var errors)) return new ErrorResponse("validation_failed", new { status = "validation_failed", diagnostics = errors ?? Array.Empty() }); else if (errors != null && errors.Length > 0) - Debug.LogWarning($"Script validation warnings for {name}:\n" + string.Join("\n", errors)); + McpLog.Warn($"Script validation warnings for {name}:\n" + string.Join("\n", errors)); // Atomic write with backup; schedule refresh // Decide refresh behavior @@ -2310,7 +2310,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning($"Could not load UnityEngine assembly: {ex.Message}"); + McpLog.Warn($"Could not load UnityEngine assembly: {ex.Message}"); } #if UNITY_EDITOR @@ -2320,7 +2320,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning($"Could not load UnityEditor assembly: {ex.Message}"); + McpLog.Warn($"Could not load UnityEditor assembly: {ex.Message}"); } // Get Unity project assemblies @@ -2337,7 +2337,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogWarning($"Could not load Unity project assemblies: {ex.Message}"); + McpLog.Warn($"Could not load Unity project assemblies: {ex.Message}"); } #endif @@ -2349,7 +2349,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception ex) { - Debug.LogError($"Failed to get compilation references: {ex.Message}"); + McpLog.Error($"Failed to get compilation references: {ex.Message}"); return new System.Collections.Generic.List(); } } diff --git a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs index 0de309c..fd19bcc 100644 --- a/MCPForUnity/Editor/Tools/ManageScriptableObject.cs +++ b/MCPForUnity/Editor/Tools/ManageScriptableObject.cs @@ -597,7 +597,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogWarning($"[MCP] Could not parse '{paramName}' JSON string: {e.Message}"); + McpLog.Warn($"[MCP] Could not parse '{paramName}' JSON string: {e.Message}"); } } } diff --git a/MCPForUnity/Editor/Tools/ReadConsole.cs b/MCPForUnity/Editor/Tools/ReadConsole.cs index 9b45f08..5b7fc0c 100644 --- a/MCPForUnity/Editor/Tools/ReadConsole.cs +++ b/MCPForUnity/Editor/Tools/ReadConsole.cs @@ -111,7 +111,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError( + McpLog.Error( $"[ReadConsole] Static Initialization Failed: Could not setup reflection for LogEntries/LogEntry. Console reading/clearing will likely fail. Specific Error: {e.Message}" ); // Set members to null to prevent NullReferenceExceptions later, HandleCommand should check this. @@ -144,7 +144,7 @@ namespace MCPForUnity.Editor.Tools ) { // Log the error here as well for easier debugging in Unity Console - Debug.LogError( + McpLog.Error( "[ReadConsole] HandleCommand called but reflection members are not initialized. Static constructor might have failed silently or there's an issue." ); return new ErrorResponse( @@ -184,7 +184,7 @@ namespace MCPForUnity.Editor.Tools if (!string.IsNullOrEmpty(sinceTimestampStr)) { - Debug.LogWarning( + McpLog.Warn( "[ReadConsole] Filtering by 'since_timestamp' is not currently implemented." ); // Need a way to get timestamp per log entry. @@ -209,7 +209,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ReadConsole] Action '{action}' failed: {e}"); + McpLog.Error($"[ReadConsole] Action '{action}' failed: {e}"); return new ErrorResponse($"Internal error processing action '{action}': {e.Message}"); } } @@ -225,7 +225,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ReadConsole] Failed to clear console: {e}"); + McpLog.Error($"[ReadConsole] Failed to clear console: {e}"); return new ErrorResponse($"Failed to clear console: {e.Message}"); } } @@ -400,7 +400,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ReadConsole] Error while retrieving log entries: {e}"); + McpLog.Error($"[ReadConsole] Error while retrieving log entries: {e}"); // EndGettingEntries will be called in the finally block return new ErrorResponse($"Error retrieving log entries: {e.Message}"); } @@ -413,7 +413,7 @@ namespace MCPForUnity.Editor.Tools } catch (Exception e) { - Debug.LogError($"[ReadConsole] Failed to call EndGettingEntries: {e}"); + McpLog.Error($"[ReadConsole] Failed to call EndGettingEntries: {e}"); // Don't return error here as we might have valid data, but log it. } } diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 7f72e94..0e9d4a2 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -110,6 +110,14 @@ namespace MCPForUnity.Editor.Windows.Components.Connection if (string.IsNullOrEmpty(scope)) { scope = MCPServiceLocator.Server.IsLocalUrl() ? "local" : "remote"; + try + { + EditorPrefs.SetString(EditorPrefKeys.HttpTransportScope, scope); + } + catch + { + McpLog.Debug("Failed to set HttpTransportScope pref."); + } } transportDropdown.value = scope == "remote" ? TransportProtocol.HTTPRemote : TransportProtocol.HTTPLocal; diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs index c99b733..edd5b2e 100644 --- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs +++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs @@ -87,7 +87,7 @@ namespace MCPForUnity.Editor.Windows if (visualTree == null) { - Debug.LogError("Failed to load EditorPrefsWindow.uxml template"); + McpLog.Error("Failed to load EditorPrefsWindow.uxml template"); return; } @@ -98,7 +98,7 @@ namespace MCPForUnity.Editor.Windows if (itemTemplate == null) { - Debug.LogError("Failed to load EditorPrefItem.uxml template"); + McpLog.Error("Failed to load EditorPrefItem.uxml template"); return; } @@ -251,7 +251,7 @@ namespace MCPForUnity.Editor.Windows { if (itemTemplate == null) { - Debug.LogError("Item template not loaded"); + McpLog.Error("Item template not loaded"); return new VisualElement(); } diff --git a/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs b/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs index 3636dd3..aa7e77f 100644 --- a/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs +++ b/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs @@ -341,7 +341,7 @@ namespace MCPForUnity.Runtime.Serialization #else // Runtime deserialization is tricky without AssetDatabase/EditorUtility // Maybe log a warning and return null or existingValue? - Debug.LogWarning("UnityEngineObjectConverter cannot deserialize complex objects in non-Editor mode."); + McpLog.Warn("UnityEngineObjectConverter cannot deserialize complex objects in non-Editor mode."); // Skip the token to avoid breaking the reader if (reader.TokenType == JsonToken.StartObject) JObject.Load(reader); else if (reader.TokenType == JsonToken.String) reader.ReadAsString(); diff --git a/README-zh.md b/README-zh.md index 771750a..703ef6b 100644 --- a/README-zh.md +++ b/README-zh.md @@ -97,8 +97,9 @@ MCP for Unity 使用两个组件连接您的工具: ### å‰ç½®è¦æ±‚ +如果你**䏿˜¯**通过 Unity Asset Store 安装,则还需è¦å®‰è£…以下内容: + * **Python:** 版本 3.10 或更新。[下载 Python](https://www.python.org/downloads/) - * **Unity Hub 和编辑器:** 版本 2021.3 LTS 或更新。[下载 Unity](https://unity.com/download) * **uv(Python 工具链管ç†å™¨ï¼‰ï¼š** ```bash # macOS / Linux @@ -109,34 +110,44 @@ MCP for Unity 使用两个组件连接您的工具: # 文档: https://docs.astral.sh/uv/getting-started/installation/ ``` - + +所有安装方å¼éƒ½éœ€è¦ä»¥ä¸‹å†…容: + + * **Unity Hub 和编辑器:** 版本 2021.3 LTS 或更新。[下载 Unity](https://unity.com/download) * **MCP 客户端:** [Claude Desktop](https://claude.ai/download) | [Claude Code](https://github.com/anthropics/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [Visual Studio Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [Windsurf](https://windsurf.com) | 其他客户端å¯é€šè¿‡æ‰‹åЍé…置使用 -*
[å¯é€‰] Roslyn ç”¨äºŽé«˜çº§è„šæœ¬éªŒè¯ +
[å¯é€‰] Roslyn ç”¨äºŽé«˜çº§è„šæœ¬éªŒè¯ - 对于æ•获未定义命å空间ã€ç±»åž‹å’Œæ–¹æ³•çš„**严格**验è¯çº§åˆ«ï¼š + 对于æ•获未定义命å空间ã€ç±»åž‹å’Œæ–¹æ³•çš„**严格**验è¯çº§åˆ«ï¼š - **方法 1:Unity çš„ NuGet(推è)** - 1. 安装 [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) - 2. å‰å¾€ `Window > NuGet Package Manager` - 3. æœç´¢ `Microsoft.CodeAnalysis`,选择版本 4.14.0 并安装包 - 4. åŒæ—¶å®‰è£…包 `SQLitePCLRaw.core` å’Œ `SQLitePCLRaw.bundle_e_sqlite3`。 - 5. å‰å¾€ `Player Settings > Scripting Define Symbols` - 6. 添加 `USE_ROSLYN` - 7. é‡å¯ Unity + **方法 1:Unity çš„ NuGet(推è)** + 1. 安装 [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) + 2. å‰å¾€ `Window > NuGet Package Manager` + 3. æœç´¢ `Microsoft.CodeAnalysis`,选择版本 4.14.0 并安装包 + 4. åŒæ—¶å®‰è£…包 `SQLitePCLRaw.core` å’Œ `SQLitePCLRaw.bundle_e_sqlite3`。 + 5. å‰å¾€ `Player Settings > Scripting Define Symbols` + 6. 添加 `USE_ROSLYN` + 7. é‡å¯ Unity - **方法 2:手动 DLL 安装** - 1. 从 [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/) 下载 Microsoft.CodeAnalysis.CSharp.dll å’Œä¾èµ–项 - 2. å°† DLL 放置在 `Assets/Plugins/` 文件夹中 - 3. ç¡®ä¿ .NET 兼容性设置正确 - 4. å°† `USE_ROSLYN` æ·»åŠ åˆ°è„šæœ¬å®šä¹‰ç¬¦å· - 5. é‡å¯ Unity + **方法 2:手动 DLL 安装** + 1. 从 [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/) 下载 Microsoft.CodeAnalysis.CSharp.dll å’Œä¾èµ–项 + 2. å°† DLL 放置在 `Assets/Plugins/` 文件夹中 + 3. ç¡®ä¿ .NET 兼容性设置正确 + 4. å°† `USE_ROSLYN` æ·»åŠ åˆ°è„šæœ¬å®šä¹‰ç¬¦å· + 5. é‡å¯ Unity - **注æ„:** 没有 Roslyn 时,脚本验è¯ä¼šå›žé€€åˆ°åŸºæœ¬ç»“构检查。Roslyn å¯ç”¨å®Œæ•´çš„ C# 编译器诊断和精确错误报告。
+ **注æ„:** 没有 Roslyn 时,脚本验è¯ä¼šå›žé€€åˆ°åŸºæœ¬ç»“构检查。Roslyn å¯ç”¨å®Œæ•´çš„ C# 编译器诊断和精确错误报告。
--- ### 🌟 步骤 1:安装 Unity 包 +#### 通过 Unity Asset Store 安装 + +1. 在æµè§ˆå™¨ä¸­æ‰“开:https://assetstore.unity.com/packages/tools/generative-ai/mcp-for-unity-ai-driven-development-329908 +2. 点击 `Add to My Assets`。 +3. 在 Unity 编辑器中,å‰å¾€ `Window > Package Manager`。 +4. 将该资æºä¸‹è½½å¹¶å¯¼å…¥åˆ°ä½ çš„项目中 + #### 通过 Git URL 安装 1. 打开您的 Unity 项目。 @@ -150,7 +161,7 @@ MCP for Unity 使用两个组件连接您的工具: **需è¦é”定版本?** 使用带标签的 URL(更新时需å¸è½½å¹¶é‡æ–°å®‰è£…): ``` -https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v8.0.0 +https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v8.6.0 ``` #### 通过 OpenUPM 安装 diff --git a/README.md b/README.md index 7454fbd..57c1497 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![Discord](https://img.shields.io/badge/discord-join-red.svg?logo=discord&logoColor=white)](https://discord.gg/y4p8KfzrN4) [![](https://img.shields.io/badge/Website-Visit-purple)](https://www.coplay.dev/?ref=unity-mcp) [![](https://img.shields.io/badge/Unity-000000?style=flat&logo=unity&logoColor=blue 'Unity')](https://unity.com/releases/editor/archive) +[![Unity Asset Store](https://img.shields.io/badge/Unity%20Asset%20Store-Get%20Package-FF6A00?style=flat&logo=unity&logoColor=white)](https://assetstore.unity.com/packages/tools/generative-ai/mcp-for-unity-ai-driven-development-329908) [![python](https://img.shields.io/badge/Python-3.10+-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org) [![](https://badge.mcpx.dev?status=on 'MCP Enabled')](https://modelcontextprotocol.io/introduction) ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/CoplayDev/unity-mcp) @@ -18,7 +19,7 @@ MCP for Unity acts as a bridge, allowing AI assistants (Claude, Cursor, Antigravity, VS Code, etc) to interact directly with your Unity Editor via a local **MCP (Model Context Protocol) Client**. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity. -MCP for Unity screenshot +MCP for Unity building a scene --- @@ -110,8 +111,9 @@ MCP for Unity connects your tools using two components: ### Prerequisites +If you are **not** installing via the Unity Asset Store, you will need to install the following: + * **Python:** Version 3.10 or newer. [Download Python](https://www.python.org/downloads/) - * **Unity Hub & Editor:** Version 2021.3 LTS or newer. [Download Unity](https://unity.com/download) * **uv (Python toolchain manager):** ```bash # macOS / Linux @@ -122,34 +124,45 @@ MCP for Unity connects your tools using two components: # Docs: https://docs.astral.sh/uv/getting-started/installation/ ``` - + +All installations require these: + + * **Unity Hub & Editor:** Version 2021.3 LTS or newer. [Download Unity](https://unity.com/download) * **An MCP Client:** : [Claude Desktop](https://claude.ai/download) | [Claude Code](https://github.com/anthropics/claude-code) | [Cursor](https://www.cursor.com/en/downloads) | [Visual Studio Code Copilot](https://code.visualstudio.com/docs/copilot/overview) | [Windsurf](https://windsurf.com) | Others work with manual config - *
[Optional] Roslyn for Advanced Script Validation +
[Optional] Roslyn for Advanced Script Validation - For **Strict** validation level that catches undefined namespaces, types, and methods: + For **Strict** validation level that catches undefined namespaces, types, and methods: - **Method 1: NuGet for Unity (Recommended)** - 1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) - 2. Go to `Window > NuGet Package Manager` - 3. Search for `Microsoft.CodeAnalysis`, select version 4.14.0, and install the package - 4. Also install package `SQLitePCLRaw.core` and `SQLitePCLRaw.bundle_e_sqlite3`. - 5. Go to `Player Settings > Scripting Define Symbols` - 6. Add `USE_ROSLYN` - 7. Restart Unity + **Method 1: NuGet for Unity (Recommended)** + 1. Install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) + 2. Go to `Window > NuGet Package Manager` + 3. Search for `Microsoft.CodeAnalysis`, select version 4.14.0, and install the package + 4. Also install package `SQLitePCLRaw.core` and `SQLitePCLRaw.bundle_e_sqlite3`. + 5. Go to `Player Settings > Scripting Define Symbols` + 6. Add `USE_ROSLYN` + 7. Restart Unity - **Method 2: Manual DLL Installation** - 1. Download Microsoft.CodeAnalysis.CSharp.dll and dependencies from [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/) - 2. Place DLLs in `Assets/Plugins/` folder - 3. Ensure .NET compatibility settings are correct - 4. Add `USE_ROSLYN` to Scripting Define Symbols - 5. Restart Unity + **Method 2: Manual DLL Installation** + 1. Download Microsoft.CodeAnalysis.CSharp.dll and dependencies from [NuGet](https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp/) + 2. Place DLLs in `Assets/Plugins/` folder + 3. Ensure .NET compatibility settings are correct + 4. Add `USE_ROSLYN` to Scripting Define Symbols + 5. Restart Unity - **Note:** Without Roslyn, script validation falls back to basic structural checks. Roslyn enables full C# compiler diagnostics with precise error reporting.
+ **Note:** Without Roslyn, script validation falls back to basic structural checks. Roslyn enables full C# compiler diagnostics with precise error reporting.
--- ### 🌟 Step 1: Install the Unity Package +#### To install via the Unity Asset Store + +1. In your browser, navigate to https://assetstore.unity.com/packages/tools/generative-ai/mcp-for-unity-ai-driven-development-329908 +2. Click `Add to My Assets`. +3. In the Unity Editor, go to`Window > Package Manager`. +4. Download and import the asset to your project + + #### To install via Git URL 1. Open your Unity project. diff --git a/TestProjects/AssetStoreUploads/.gitignore b/TestProjects/AssetStoreUploads/.gitignore new file mode 100644 index 0000000..a65ab81 --- /dev/null +++ b/TestProjects/AssetStoreUploads/.gitignore @@ -0,0 +1,106 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +.utmp/ +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ +*.log + +# By default unity supports Blender asset imports, *.blend1 blender files do not need to be commited to version control. +*.blend1 +*.blend1.meta + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* +# Jetbrains Rider personal-layer settings +*.DotSettings.user + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Mono auto generated files +mono_crash.* + +# Builds +*.apk +*.aab +*.unitypackage +*.unitypackage.meta +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# TestRunner generated files +InitTestScene*.unity* + +# Addressables default ignores, before user customizations +/ServerData +/[Aa]ssets/StreamingAssets/aa* +/[Aa]ssets/AddressableAssetsData/link.xml* +/[Aa]ssets/Addressables_Temp* +# By default, Addressables content builds will generate addressables_content_state.bin +# files in platform-specific subfolders, for example: +# /Assets/AddressableAssetsData/OSX/addressables_content_state.bin +/[Aa]ssets/AddressableAssetsData/*/*.bin* + +# Visual Scripting auto-generated files +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Flow/UnitOptions.db.meta +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers +/[Aa]ssets/Unity.VisualScripting.Generated/VisualScripting.Core/Property Providers.meta + +# Auto-generated scenes by play mode tests +/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity* + +.vscode +.cursor +.windsurf +.claude +.DS_Store +boot.config diff --git a/TestProjects/UnityMCPTests/Assets/Scripts/TestScriptableObjectInstance.asset.meta b/TestProjects/AssetStoreUploads/Assets/Readme.asset.meta similarity index 78% rename from TestProjects/UnityMCPTests/Assets/Scripts/TestScriptableObjectInstance.asset.meta rename to TestProjects/AssetStoreUploads/Assets/Readme.asset.meta index c74c318..ab3ad45 100644 --- a/TestProjects/UnityMCPTests/Assets/Scripts/TestScriptableObjectInstance.asset.meta +++ b/TestProjects/AssetStoreUploads/Assets/Readme.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 93766a50487224f02b29aae42971e08b +guid: 8105016687592461f977c054a80ce2f2 NativeFormatImporter: externalObjects: {} mainObjectFileID: 0 diff --git a/TestProjects/AssetStoreUploads/Assets/Scenes.meta b/TestProjects/AssetStoreUploads/Assets/Scenes.meta new file mode 100644 index 0000000..4ce6ae3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5537d31886f841d58d487c5db45aaa0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity b/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity new file mode 100644 index 0000000..89d7244 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity @@ -0,0 +1,407 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.18028305, g: 0.22571313, b: 0.3069213, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &330585543 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 330585546} + - component: {fileID: 330585545} + - component: {fileID: 330585544} + - component: {fileID: 330585547} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &330585544 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 +--- !u!20 &330585545 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &330585546 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &330585547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 330585543} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 1 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 +--- !u!1 &410087039 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 410087041} + - component: {fileID: 410087040} + - component: {fileID: 410087042} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &410087040 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 2 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 5000 + m_UseColorTemperature: 1 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &410087041 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &410087042 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 410087039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 1 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} +--- !u!1 &832575517 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 832575519} + - component: {fileID: 832575518} + m_Layer: 0 + m_Name: Global Volume + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &832575518 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 172515602e62fb746b5d573b38a5fe58, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IsGlobal: 1 + priority: 0 + blendDistance: 0 + weight: 1 + sharedProfile: {fileID: 11400000, guid: a6560a915ef98420e9faacc1c7438823, type: 2} +--- !u!4 &832575519 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 832575517} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity.meta b/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..9531828 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 99c9720ab356a0642a771bea13969a05 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings.meta b/TestProjects/AssetStoreUploads/Assets/Settings.meta new file mode 100644 index 0000000..39b94dd --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 709f11a7f3c4041caa4ef136ea32d874 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/SampleSceneProfile.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/SampleSceneProfile.asset.meta new file mode 100644 index 0000000..f8cce64 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/SampleSceneProfile.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6560a915ef98420e9faacc1c7438823 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced-Renderer.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced-Renderer.asset.meta new file mode 100644 index 0000000..8fa7f17 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced-Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e634585d5c4544dd297acaee93dc2beb +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced.asset.meta new file mode 100644 index 0000000..f524db0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Balanced.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e1260c1148f6143b28bae5ace5e9c5d1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity-Renderer.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity-Renderer.asset.meta new file mode 100644 index 0000000..bcdff02 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity-Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c40be3174f62c4acf8c1216858c64956 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity.asset.meta new file mode 100644 index 0000000..7416e17 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-HighFidelity.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b7fd9122c28c4d15b667c7040e3b3fd +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant-Renderer.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant-Renderer.asset.meta new file mode 100644 index 0000000..912ff60 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant-Renderer.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 707360a9c581a4bd7aa53bfeb1429f71 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant.asset.meta b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant.asset.meta new file mode 100644 index 0000000..264c9c5 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/Settings/URP-Performant.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d0e2fc18fe036412f8223b3b3d9ad574 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/TutorialInfo.meta b/TestProjects/AssetStoreUploads/Assets/TutorialInfo.meta new file mode 100644 index 0000000..a700bca --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/TutorialInfo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ba062aa6c92b140379dbc06b43dd3b9b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons.meta b/TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons.meta new file mode 100644 index 0000000..1d19fb9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8a0c9218a650547d98138cd835033977 +folderAsset: yes +timeCreated: 1484670163 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons/URP.png b/TestProjects/AssetStoreUploads/Assets/TutorialInfo/Icons/URP.png new file mode 100644 index 0000000000000000000000000000000000000000..6194a807e27158f864a7c7677f4cbf62d8b94503 GIT binary patch literal 24069 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 HejoV
{message}"; + private static string FormatError(object message) => $"[AST Error] {message}"; + + + private static bool s_debugModeEnabled = EditorPrefs.GetBool(Constants.Debug.DebugModeKey); + + public static bool DebugModeEnabled + { + get => s_debugModeEnabled; + set { s_debugModeEnabled = value; EditorPrefs.SetBool(Constants.Debug.DebugModeKey, value); } + } + + public static void Log(object message) + { + LogMessage(message, LogType.Info); + } + + public static void LogWarning(object message) + { + LogMessage(message, LogType.Warning); + } + + public static void LogError(object message) + { + LogMessage(message, LogType.Error); + } + + private static void LogMessage(object message, LogType type) + { + if (!DebugModeEnabled) + return; + + switch (type) + { + case LogType.Info: + Debug.Log(FormatInfo(message)); + break; + case LogType.Warning: + Debug.LogWarning(FormatWarning(message)); + break; + case LogType.Error: + Debug.LogError(FormatError(message)); + break; + default: + Debug.Log(message); + break; + } + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASDebug.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASDebug.cs.meta new file mode 100644 index 0000000..2f4aab7 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASDebug.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 478caa497d99100429a0509fa487bfe4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs new file mode 100644 index 0000000..7acbb55 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Utility +{ + internal class ASToolsPreferences + { + private static ASToolsPreferences s_instance; + public static ASToolsPreferences Instance => s_instance ?? (s_instance = new ASToolsPreferences()); + + public static event Action OnSettingsChange; + + private ASToolsPreferences() + { + Load(); + } + + private void Load() + { + CheckForUpdates = PlayerPrefs.GetInt("AST_CheckForUpdates", 1) == 1; + LegacyVersionCheck = PlayerPrefs.GetInt("AST_LegacyVersionCheck", 1) == 1; + UploadVersionCheck = PlayerPrefs.GetInt("AST_UploadVersionCheck", 1) == 1; + DisplayHiddenMetaDialog = PlayerPrefs.GetInt("AST_HiddenFolderMetaCheck", 1) == 1; + EnableSymlinkSupport = PlayerPrefs.GetInt("AST_EnableSymlinkSupport", 0) == 1; + UseLegacyExporting = PlayerPrefs.GetInt("AST_UseLegacyExporting", 0) == 1; + } + + public void Save(bool triggerSettingsChange = false) + { + PlayerPrefs.SetInt("AST_CheckForUpdates", CheckForUpdates ? 1 : 0); + PlayerPrefs.SetInt("AST_LegacyVersionCheck", LegacyVersionCheck ? 1 : 0); + PlayerPrefs.SetInt("AST_UploadVersionCheck", UploadVersionCheck ? 1 : 0); + PlayerPrefs.SetInt("AST_HiddenFolderMetaCheck", DisplayHiddenMetaDialog ? 1 : 0); + PlayerPrefs.SetInt("AST_EnableSymlinkSupport", EnableSymlinkSupport ? 1 : 0); + PlayerPrefs.SetInt("AST_UseLegacyExporting", UseLegacyExporting ? 1 : 0); + PlayerPrefs.Save(); + + if (triggerSettingsChange) + OnSettingsChange?.Invoke(); + } + + /// + /// Periodically check if an update for the Asset Store Publishing Tools is available + /// + public bool CheckForUpdates; + + /// + /// Check if legacy Asset Store Tools are in the Project + /// + public bool LegacyVersionCheck; + + /// + /// Enables a DisplayDialog when hidden folders are found to be missing meta files + /// + public bool DisplayHiddenMetaDialog; + + /// + /// Check if the package has been uploaded from a correct Unity version at least once + /// + public bool UploadVersionCheck; + + /// + /// Enables Junction symlink support + /// + public bool EnableSymlinkSupport; + + /// + /// Enables legacy exporting for Folder Upload workflow + /// + public bool UseLegacyExporting; + } + + internal class ASToolsPreferencesProvider : SettingsProvider + { + private const string SettingsPath = "Project/Asset Store Tools"; + + private class Styles + { + public static readonly GUIContent CheckForUpdatesLabel = EditorGUIUtility.TrTextContent("Check for Updates", "Periodically check if an update for the Asset Store Publishing Tools is available."); + public static readonly GUIContent LegacyVersionCheckLabel = EditorGUIUtility.TrTextContent("Legacy ASTools Check", "Enable Legacy Asset Store Tools version checking."); + public static readonly GUIContent UploadVersionCheckLabel = EditorGUIUtility.TrTextContent("Upload Version Check", "Check if the package has been uploader from a correct Unity version at least once."); + public static readonly GUIContent DisplayHiddenMetaDialogLabel = EditorGUIUtility.TrTextContent("Display Hidden Folder Meta Dialog", "Show a DisplayDialog when hidden folders are found to be missing meta files.\nNote: this only affects hidden folders ending with a '~' character"); + public static readonly GUIContent EnableSymlinkSupportLabel = EditorGUIUtility.TrTextContent("Enable Symlink Support", "Enable Junction Symlink support. Note: folder selection validation will take longer."); + public static readonly GUIContent UseLegacyExportingLabel = EditorGUIUtility.TrTextContent("Use Legacy Exporting", "Enabling this option uses native Unity methods when exporting packages for the Folder Upload workflow.\nNote: individual package dependency selection when choosing to 'Include Package Manifest' is unavailable when this option is enabled."); + public static readonly GUIContent UseCustomPreviewsLabel = EditorGUIUtility.TrTextContent("Enable High Quality Previews (experimental)", "Override native asset preview retrieval with higher-quality preview generation"); + } + + public static void OpenSettings() + { + SettingsService.OpenProjectSettings(SettingsPath); + } + + private ASToolsPreferencesProvider(string path, SettingsScope scopes, IEnumerable keywords = null) + : base(path, scopes, keywords) { } + + public override void OnGUI(string searchContext) + { + var preferences = ASToolsPreferences.Instance; + + EditorGUI.BeginChangeCheck(); + using (CreateSettingsWindowGUIScope()) + { + preferences.CheckForUpdates = EditorGUILayout.Toggle(Styles.CheckForUpdatesLabel, preferences.CheckForUpdates); + preferences.LegacyVersionCheck = EditorGUILayout.Toggle(Styles.LegacyVersionCheckLabel, preferences.LegacyVersionCheck); + preferences.UploadVersionCheck = EditorGUILayout.Toggle(Styles.UploadVersionCheckLabel, preferences.UploadVersionCheck); + preferences.DisplayHiddenMetaDialog = EditorGUILayout.Toggle(Styles.DisplayHiddenMetaDialogLabel, preferences.DisplayHiddenMetaDialog); + preferences.EnableSymlinkSupport = EditorGUILayout.Toggle(Styles.EnableSymlinkSupportLabel, preferences.EnableSymlinkSupport); + preferences.UseLegacyExporting = EditorGUILayout.Toggle(Styles.UseLegacyExportingLabel, preferences.UseLegacyExporting); + } + + if (EditorGUI.EndChangeCheck()) + { + ASToolsPreferences.Instance.Save(true); + } + } + + [SettingsProvider] + public static SettingsProvider CreateAssetStoreToolsSettingProvider() + { + var provider = new ASToolsPreferencesProvider(SettingsPath, SettingsScope.Project, GetSearchKeywordsFromGUIContentProperties()); + return provider; + } + + private IDisposable CreateSettingsWindowGUIScope() + { + var unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow)); + var type = unityEditorAssembly.GetType("UnityEditor.SettingsWindow+GUIScope"); + return Activator.CreateInstance(type) as IDisposable; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs.meta new file mode 100644 index 0000000..56807b0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsPreferences.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b75179c8d22a35b42a543d6fa7857ce0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs new file mode 100644 index 0000000..a3b639c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs @@ -0,0 +1,250 @@ +using AssetStoreTools.Api; +using System; +using System.Linq; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Utility +{ + [InitializeOnLoad] + internal class ASToolsUpdater : AssetStoreToolsWindow + { + protected override string WindowTitle => "Asset Store Tools Update Check"; + + private static IAssetStoreApi _api; + + private VisualElement _loadingContainer; + private VisualElement _versionInfoContainer; + + private Image _loadingImage; + private double _lastTimeSinceStartup; + private double _timeSinceLoadingImageChange; + private int _loadingImageIndex; + + private static bool _updateCheckPerformed + { + get + { + return SessionState.GetBool("AST_UpdateChecked", false); + } + set + { + SessionState.SetBool("AST_UpdateChecked", value); + } + } + + static ASToolsUpdater() + { + _api = new AssetStoreApi(new AssetStoreClient()); + // Retrieving cached SessionState/PlayerPrefs values is not allowed from an instance field initializer + EditorApplication.update += CheckForUpdatesAfterEditorUpdate; + } + + private static async void CheckForUpdatesAfterEditorUpdate() + { + EditorApplication.update -= CheckForUpdatesAfterEditorUpdate; + + if (!ShouldCheckForUpdates()) + return; + + await CheckForUpdates((success, currentVersion, latestVersion) => + { + if (success && currentVersion < latestVersion) + { + AssetStoreTools.OpenUpdateChecker(); + } + }); + } + + private static bool ShouldCheckForUpdates() + { + if (!ASToolsPreferences.Instance.CheckForUpdates) + return false; + + return _updateCheckPerformed == false; + } + + private static async Task CheckForUpdates(Action OnUpdatesChecked) + { + _updateCheckPerformed = true; + var latestVersionResult = await _api.GetLatestAssetStoreToolsVersion(); + if (!latestVersionResult.Success) + { + OnUpdatesChecked?.Invoke(false, null, null); + return; + } + + Version currentVersion = null; + Version latestVersion = null; + + try + { + var latestVersionStr = latestVersionResult.Version; + var currentVersionStr = PackageUtility.GetAllPackages().FirstOrDefault(x => x.name == "com.unity.asset-store-tools").version; + + currentVersion = new Version(currentVersionStr); + latestVersion = new Version(latestVersionStr); + } + catch + { + OnUpdatesChecked?.Invoke(false, null, null); + } + + OnUpdatesChecked?.Invoke(true, currentVersion, latestVersion); + } + + protected override void Init() + { + rootVisualElement.styleSheets.Add(StyleSelector.UpdaterWindow.UpdaterWindowStyle); + rootVisualElement.styleSheets.Add(StyleSelector.UpdaterWindow.UpdaterWindowTheme); + + SetupLoadingSpinner(); + _ = CheckForUpdates(OnVersionsRetrieved); + } + + private void OnVersionsRetrieved(bool success, Version currentVersion, Version latestVersion) + { + if (_loadingContainer != null) + _loadingContainer.style.display = DisplayStyle.None; + + if (success) + { + SetupVersionInfo(currentVersion, latestVersion); + } + else + { + SetupFailInfo(); + } + } + + private void SetupLoadingSpinner() + { + _loadingContainer = new VisualElement(); + _loadingContainer.AddToClassList("updater-loading-container"); + _loadingImage = new Image(); + EditorApplication.update += LoadingSpinLoop; + + _loadingContainer.Add(_loadingImage); + rootVisualElement.Add(_loadingContainer); + } + + private void SetupVersionInfo(Version currentVersion, Version latestVersion) + { + _versionInfoContainer = new VisualElement(); + _versionInfoContainer.AddToClassList("updater-info-container"); + + AddDescriptionLabels(currentVersion, latestVersion); + AddUpdateButtons(currentVersion, latestVersion); + AddCheckForUpdatesToggle(); + + rootVisualElement.Add(_versionInfoContainer); + } + + private void AddDescriptionLabels(Version currentVersion, Version latestVersion) + { + var descriptionText = currentVersion < latestVersion ? + "An update to the Asset Store Publishing Tools is available. Updating to the latest version is highly recommended." : + "Asset Store Publishing Tools are up to date!"; + + var labelContainer = new VisualElement(); + labelContainer.AddToClassList("updater-info-container-labels"); + var descriptionLabel = new Label(descriptionText); + descriptionLabel.AddToClassList("updater-info-container-labels-description"); + + var currentVersionRow = new VisualElement(); + currentVersionRow.AddToClassList("updater-info-container-labels-row"); + var latestVersionRow = new VisualElement(); + latestVersionRow.AddToClassList("updater-info-container-labels-row"); + + var currentVersionLabel = new Label("Current version:"); + currentVersionLabel.AddToClassList("updater-info-container-labels-row-identifier"); + var latestVersionLabel = new Label("Latest version:"); + latestVersionLabel.AddToClassList("updater-info-container-labels-row-identifier"); + + var currentVersionLabelValue = new Label(currentVersion.ToString()); + var latestVersionLabelValue = new Label(latestVersion.ToString()); + + currentVersionRow.Add(currentVersionLabel); + currentVersionRow.Add(currentVersionLabelValue); + latestVersionRow.Add(latestVersionLabel); + latestVersionRow.Add(latestVersionLabelValue); + + labelContainer.Add(descriptionLabel); + labelContainer.Add(currentVersionRow); + labelContainer.Add(latestVersionRow); + + _versionInfoContainer.Add(labelContainer); + } + + private void AddUpdateButtons(Version currentVersion, Version latestVersion) + { + if (currentVersion >= latestVersion) + return; + + var buttonContainer = new VisualElement(); + buttonContainer.AddToClassList("updater-info-container-buttons"); + var latestVersionButton = new Button(() => Application.OpenURL(Constants.Updater.AssetStoreToolsUrl)) { text = "Get the latest version" }; + var skipVersionButton = new Button(Close) { text = "Skip for now" }; + + buttonContainer.Add(latestVersionButton); + buttonContainer.Add(skipVersionButton); + + _versionInfoContainer.Add(buttonContainer); + } + + private void AddCheckForUpdatesToggle() + { + var toggleContainer = new VisualElement(); + toggleContainer.AddToClassList("updater-info-container-toggle"); + var checkForUpdatesToggle = new Toggle() { text = "Check for Updates", value = ASToolsPreferences.Instance.CheckForUpdates }; + checkForUpdatesToggle.RegisterValueChangedCallback(OnCheckForUpdatesToggleChanged); + + toggleContainer.Add(checkForUpdatesToggle); + _versionInfoContainer.Add(toggleContainer); + } + + private void OnCheckForUpdatesToggleChanged(ChangeEvent evt) + { + ASToolsPreferences.Instance.CheckForUpdates = evt.newValue; + ASToolsPreferences.Instance.Save(); + } + + private void SetupFailInfo() + { + var failContainer = new VisualElement(); + failContainer.AddToClassList("updater-fail-container"); + + var failImage = new Image(); + var failDescription = new Label("Asset Store Publishing Tools could not retrieve information about the latest version."); + + failContainer.Add(failImage); + failContainer.Add(failDescription); + + rootVisualElement.Add(failContainer); + } + + private void LoadingSpinLoop() + { + var currentTimeSinceStartup = EditorApplication.timeSinceStartup; + var deltaTime = EditorApplication.timeSinceStartup - _lastTimeSinceStartup; + _lastTimeSinceStartup = currentTimeSinceStartup; + + _timeSinceLoadingImageChange += deltaTime; + if (_timeSinceLoadingImageChange < 0.075) + return; + + _timeSinceLoadingImageChange = 0; + + _loadingImage.image = EditorGUIUtility.IconContent($"WaitSpin{_loadingImageIndex++:00}").image; + if (_loadingImageIndex > 11) + _loadingImageIndex = 0; + } + + private void OnDestroy() + { + EditorApplication.update -= LoadingSpinLoop; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs.meta new file mode 100644 index 0000000..d0e4183 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ASToolsUpdater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ac370b9d2279ed4c9faec7134ba2759 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs new file mode 100644 index 0000000..a0bea03 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs @@ -0,0 +1,266 @@ +using System.IO; +using CacheConstants = AssetStoreTools.Constants.Cache; + +namespace AssetStoreTools.Utility +{ + internal static class CacheUtil + { + public static bool GetFileFromTempCache(string fileName, out string filePath) + { + return GetCacheFile(CacheConstants.TempCachePath, fileName, out filePath); + } + + public static bool GetFileFromPersistentCache(string fileName, out string filePath) + { + return GetCacheFile(CacheConstants.PersistentCachePath, fileName, out filePath); + } + + public static bool GetFileFromProjectPersistentCache(string projectPath, string fileName, out string filePath) + { + return GetCacheFile(Path.Combine(projectPath, CacheConstants.PersistentCachePath), fileName, out filePath); + } + + private static bool GetCacheFile(string rootPath, string fileName, out string filePath) + { + filePath = Path.Combine(rootPath, fileName); + return File.Exists(filePath); + } + + public static void CreateFileInTempCache(string fileName, object content, bool overwrite) + { + CreateCacheFile(CacheConstants.TempCachePath, fileName, content, overwrite); + } + + public static void CreateFileInPersistentCache(string fileName, object content, bool overwrite) + { + CreateCacheFile(CacheConstants.PersistentCachePath, fileName, content, overwrite); + } + + private static void CreateCacheFile(string rootPath, string fileName, object content, bool overwrite) + { + if (!Directory.Exists(rootPath)) + Directory.CreateDirectory(rootPath); + + var fullPath = Path.Combine(rootPath, fileName); + + bool willUpdate = false; + if (File.Exists(fullPath)) + { + if (overwrite) + { + File.Delete(fullPath); + willUpdate = true; + } + else + return; + } + + switch (content) + { + case byte[] bytes: + File.WriteAllBytes(fullPath, bytes); + break; + default: + File.WriteAllText(fullPath, content.ToString()); + break; + } + + var keyword = willUpdate ? "Updating" : "Creating"; + ASDebug.Log($"{keyword} cache file: '{fullPath}'"); + } + + public static void DeleteFileFromTempCache(string fileName) + { + DeleteFileFromCache(CacheConstants.TempCachePath, fileName); + } + + public static void DeleteFileFromPersistentCache(string fileName) + { + DeleteFileFromCache(CacheConstants.PersistentCachePath, fileName); + } + + private static void DeleteFileFromCache(string rootPath, string fileName) + { + var path = Path.Combine(rootPath, fileName); + if (File.Exists(path)) + File.Delete(path); + } + + //private static void CreateFileInPersistentCache(string fileName, object content, bool overwrite) + //{ + // CreateCacheFile(CacheConstants.PersistentCachePath, fileName, content, overwrite); + //} + + //private static void CreateCacheFile(string rootPath, string fileName, object content, bool overwrite) + //{ + // if (!Directory.Exists(rootPath)) + // Directory.CreateDirectory(rootPath); + + // var fullPath = Path.Combine(rootPath, fileName); + + // if (File.Exists(fullPath)) + // { + // if (overwrite) + // File.Delete(fullPath); + // else + // return; + // } + + // switch (content) + // { + // case byte[] bytes: + // File.WriteAllBytes(fullPath, bytes); + // break; + // default: + // File.WriteAllText(fullPath, content.ToString()); + // break; + // } + // ASDebug.Log($"Creating cached file: '{fullPath}'"); + //} + + //public static void ClearTempCache() + //{ + // if (!File.Exists(Path.Combine(CacheConstants.TempCachePath, CacheConstants.PackageDataFile))) + // return; + + + // // Cache consists of package data and package texture thumbnails. We don't clear + // // texture thumbnails here since they are less likely to change. They are still + // // deleted and redownloaded every project restart (because of being stored in the 'Temp' folder) + // var fullPath = Path.Combine(CacheConstants.TempCachePath, CacheConstants.PackageDataFile); + // ASDebug.Log($"Deleting cached file '{fullPath}'"); + // File.Delete(fullPath); + //} + + //public static void CachePackageMetadata(List data) + //{ + // var serializerSettings = new JsonSerializerSettings() + // { + // ContractResolver = Package.CachedPackageResolver.Instance, + // Formatting = Formatting.Indented + // }; + + // CreateFileInTempCache(CacheConstants.PackageDataFile, JsonConvert.SerializeObject(data, serializerSettings), true); + //} + + //public static void UpdatePackageMetadata(Package data) + //{ + // if (!GetCachedPackageMetadata(out var cachedData)) + // return; + + // var index = cachedData.FindIndex(x => x.PackageId.Equals(data.PackageId)); + // if (index == -1) + // { + // cachedData.Add(data); + // } + // else + // { + // cachedData.RemoveAt(index); + // cachedData.Insert(index, data); + // } + + // CachePackageMetadata(cachedData); + //} + + //public static bool GetCachedPackageMetadata(out List data) + //{ + // data = new List(); + // var path = Path.Combine(CacheConstants.TempCachePath, CacheConstants.PackageDataFile); + // if (!File.Exists(path)) + // return false; + + // try + // { + // var serializerSettings = new JsonSerializerSettings() + // { + // ContractResolver = Package.CachedPackageResolver.Instance + // }; + + // data = JsonConvert.DeserializeObject>(File.ReadAllText(path, Encoding.UTF8), serializerSettings); + // return true; + // } + // catch + // { + // return false; + // } + //} + + //public static void CacheTexture(string packageId, Texture2D texture) + //{ + // CreateFileInTempCache($"{packageId}.png", texture.EncodeToPNG(), true); + //} + + //public static bool GetCachedTexture(string packageId, out Texture2D texture) + //{ + // texture = new Texture2D(1, 1); + // var path = Path.Combine(CacheConstants.TempCachePath, $"{packageId}.png"); + // if (!File.Exists(path)) + // return false; + + // texture.LoadImage(File.ReadAllBytes(path)); + // return true; + //} + + //public static void CacheWorkflowStateData(string packageId, WorkflowStateData data) + //{ + // var fileName = $"{packageId}-workflowStateData.asset"; + // CreateFileInPersistentCache(fileName, JsonConvert.SerializeObject(data, Formatting.Indented), true); + //} + + //public static bool GetCachedWorkflowStateData(string packageId, out WorkflowStateData data) + //{ + // data = null; + // var path = Path.Combine(CacheConstants.PersistentCachePath, $"{packageId}-workflowStateData.asset"); + // if (!File.Exists(path)) + // return false; + + // data = JsonConvert.DeserializeObject(File.ReadAllText(path, Encoding.UTF8)); + // return true; + //} + + //public static void CacheValidationStateData(ValidationStateData data) + //{ + // var serializerSettings = new JsonSerializerSettings() + // { + // ContractResolver = ValidationStateDataContractResolver.Instance, + // Formatting = Formatting.Indented, + // TypeNameHandling = TypeNameHandling.Auto, + // Converters = new List() { new StringEnumConverter() } + // }; + + // CreateFileInPersistentCache(CacheConstants.ValidationResultFile, JsonConvert.SerializeObject(data, serializerSettings), true); + //} + + //public static bool GetCachedValidationStateData(out ValidationStateData data) + //{ + // return GetCachedValidationStateData(Constants.RootProjectPath, out data); + //} + + //public static bool GetCachedValidationStateData(string projectPath, out ValidationStateData data) + //{ + // data = null; + // var path = Path.Combine(projectPath, CacheConstants.PersistentCachePath, CacheConstants.ValidationResultFile); + // if (!File.Exists(path)) + // return false; + + // try + // { + // var serializerSettings = new JsonSerializerSettings() + // { + // ContractResolver = ValidationStateDataContractResolver.Instance, + // Formatting = Formatting.Indented, + // TypeNameHandling = TypeNameHandling.Auto, + // Converters = new List() { new StringEnumConverter() } + // }; + + // data = JsonConvert.DeserializeObject(File.ReadAllText(path, Encoding.UTF8), serializerSettings); + // return true; + // } + // catch (System.Exception e) + // { + // UnityEngine.Debug.LogException(e); + // return false; + // } + //} + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs.meta new file mode 100644 index 0000000..fca787f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/CacheUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e5fee0cad7655f458d9b600f4ae6d02 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs new file mode 100644 index 0000000..6be5f07 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs @@ -0,0 +1,207 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; + +namespace AssetStoreTools.Utility +{ + internal static class FileUtility + { + private class RenameInfo + { + public string OriginalName; + public string CurrentName; + } + + public static string AbsolutePathToRelativePath(string path, bool allowSymlinks) + { + if (!File.Exists(path) && !Directory.Exists(path)) + return path; + + string convertedPath = path.Replace("\\", "/"); + + var allPackages = PackageUtility.GetAllPackages(); + foreach (var package in allPackages) + { + if (Path.GetFullPath(package.resolvedPath) != Path.GetFullPath(convertedPath) + && !Path.GetFullPath(convertedPath).StartsWith(Path.GetFullPath(package.resolvedPath) + Path.DirectorySeparatorChar)) + continue; + + convertedPath = Path.GetFullPath(convertedPath) + .Replace(Path.GetFullPath(package.resolvedPath), package.assetPath) + .Replace("\\", "/"); + + return convertedPath; + } + + if (convertedPath.StartsWith(Constants.RootProjectPath)) + { + convertedPath = convertedPath.Substring(Constants.RootProjectPath.Length); + } + else + { + if (allowSymlinks && SymlinkUtil.FindSymlinkFolderRelative(convertedPath, out var symlinkPath)) + convertedPath = symlinkPath; + } + + return convertedPath; + } + + public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) + { + // Get information about the source directory + var dir = new DirectoryInfo(sourceDir); + + // Check if the source directory exists + if (!dir.Exists) + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); + + // Cache directories before we start copying + DirectoryInfo[] dirs = dir.GetDirectories(); + + // Create the destination directory + Directory.CreateDirectory(destinationDir); + + // Get the files in the source directory and copy to the destination directory + foreach (FileInfo file in dir.GetFiles()) + { + string targetFilePath = Path.Combine(destinationDir, file.Name); + file.CopyTo(targetFilePath); + } + + // If recursive and copying subdirectories, recursively call this method + if (recursive) + { + foreach (DirectoryInfo subDir in dirs) + { + string newDestinationDir = Path.Combine(destinationDir, subDir.Name); + CopyDirectory(subDir.FullName, newDestinationDir, true); + } + } + } + + public static bool ShouldHaveMeta(string assetPath) + { + if (string.IsNullOrEmpty(assetPath)) + return false; + + // Meta files never have other metas + if (assetPath.EndsWith(".meta", System.StringComparison.OrdinalIgnoreCase)) + return false; + + // File System entries ending with '~' are hidden in the context of ADB + if (assetPath.EndsWith("~")) + return false; + + // File System entries whose names start with '.' are hidden in the context of ADB + var assetName = assetPath.Replace("\\", "/").Split('/').Last(); + if (assetName.StartsWith(".")) + return false; + + return true; + } + + public static bool IsMissingMetaFiles(IEnumerable sourcePaths) + { + foreach (var sourcePath in sourcePaths) + { + if (!Directory.Exists(sourcePath)) + continue; + + var allDirectories = Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories); + foreach (var dir in allDirectories) + { + var dirInfo = new DirectoryInfo(dir); + if (!dirInfo.Name.EndsWith("~")) + continue; + + var nestedContent = dirInfo.GetFileSystemInfos("*", SearchOption.AllDirectories); + foreach (var nested in nestedContent) + { + if (!ShouldHaveMeta(nested.FullName)) + continue; + + if (!File.Exists(nested.FullName + ".meta")) + return true; + } + } + } + + return false; + } + + public static void GenerateMetaFiles(IEnumerable sourcePaths) + { + var renameInfos = new List(); + + foreach (var sourcePath in sourcePaths) + { + if (!Directory.Exists(sourcePath)) + continue; + + var hiddenDirectoriesInPath = Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories).Where(x => x.EndsWith("~")); + foreach (var hiddenDir in hiddenDirectoriesInPath) + { + var hiddenDirRelative = AbsolutePathToRelativePath(hiddenDir, ASToolsPreferences.Instance.EnableSymlinkSupport); + if (!hiddenDirRelative.StartsWith("Assets/") && !hiddenDirRelative.StartsWith("Packages/")) + { + ASDebug.LogWarning($"Path {sourcePath} is not part of the Asset Database and will be skipped"); + continue; + } + + renameInfos.Add(new RenameInfo() { CurrentName = hiddenDirRelative, OriginalName = hiddenDirRelative }); + } + } + + if (renameInfos.Count == 0) + return; + + try + { + EditorApplication.LockReloadAssemblies(); + + // Order paths from longest to shortest to avoid having to rename them multiple times + renameInfos = renameInfos.OrderByDescending(x => x.OriginalName.Length).ToList(); + + try + { + AssetDatabase.StartAssetEditing(); + foreach (var renameInfo in renameInfos) + { + renameInfo.CurrentName = renameInfo.OriginalName.TrimEnd('~'); + Directory.Move(renameInfo.OriginalName, renameInfo.CurrentName); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); + AssetDatabase.ReleaseCachedFileHandles(); + } + + // Restore the original path names in reverse order + renameInfos = renameInfos.OrderBy(x => x.OriginalName.Length).ToList(); + + try + { + AssetDatabase.StartAssetEditing(); + foreach (var renameInfo in renameInfos) + { + Directory.Move(renameInfo.CurrentName, renameInfo.OriginalName); + if (File.Exists($"{renameInfo.CurrentName}.meta")) + File.Delete($"{renameInfo.CurrentName}.meta"); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); + } + } + finally + { + EditorApplication.UnlockReloadAssemblies(); + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs.meta new file mode 100644 index 0000000..db285bb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/FileUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80819cf6868374d478a8a38ebaba8e27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs new file mode 100644 index 0000000..2b5b169 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Utility +{ + [InitializeOnLoad] + internal class LegacyToolsRemover + { + private const string MessagePart1 = "A legacy version of Asset Store Tools " + + "was detected at the following path:\n"; + private const string MessagePart2 = "\n\nHaving both the legacy and the latest version installed at the same time is not supported " + + "and might prevent the latest version from functioning properly.\n\nWould you like the legacy version to be removed automatically?"; + + static LegacyToolsRemover() + { + try + { + if (Application.isBatchMode) + return; + + CheckAndRemoveLegacyTools(); + } + catch { } + } + + private static void CheckAndRemoveLegacyTools() + { + if (!ASToolsPreferences.Instance.LegacyVersionCheck || !ProjectContainsLegacyTools(out string path)) + return; + + var relativePath = path.Substring(Application.dataPath.Length - "Assets".Length).Replace("\\", "/"); + var result = EditorUtility.DisplayDialog("Asset Store Tools", MessagePart1 + relativePath + MessagePart2, "Yes", "No"); + + // If "No" - do nothing + if (!result) + return; + + // If "Yes" - remove legacy tools + File.Delete(path); + File.Delete(path + ".meta"); + RemoveEmptyFolders(Path.GetDirectoryName(path)?.Replace("\\", "/")); + AssetDatabase.Refresh(); + + // We could also optionally prevent future execution here + // but the ProjectContainsLegacyTools() function runs in less + // than a milisecond on an empty project + } + + private static bool ProjectContainsLegacyTools(out string path) + { + path = null; + + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.ManifestModule.Name == "AssetStoreTools.dll") + { + path = assembly.Location; + break; + } + } + + if (string.IsNullOrEmpty(path)) + return false; + return true; + } + + private static void RemoveEmptyFolders(string directory) + { + if (directory.EndsWith(Application.dataPath)) + return; + + if (Directory.GetFiles(directory).Length == 0 && Directory.GetDirectories(directory).Length == 0) + { + var parentPath = Path.GetDirectoryName(directory).Replace("\\", "/"); + + Directory.Delete(directory); + File.Delete(directory + ".meta"); + + RemoveEmptyFolders(parentPath); + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs.meta new file mode 100644 index 0000000..c16ca0a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/LegacyToolsRemover.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46ead42026b1f0b4e94153e1a7e10d12 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs new file mode 100644 index 0000000..2b6862c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs @@ -0,0 +1,170 @@ +#if !UNITY_2021_1_OR_NEWER +using System; +using System.Reflection; +#endif +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.PackageManager; +using UnityEngine; +using PackageInfo = UnityEditor.PackageManager.PackageInfo; + +namespace AssetStoreTools.Utility +{ + internal static class PackageUtility + { + public class PackageInfoSampleMetadata + { + public string DisplayName; + public string Description; + public string Path; + } + + public class PackageInfoUnityVersionMetadata + { + /// + /// Major bit of the Unity version, e.g. 2021.3 + /// + public string Version; + /// + /// Minor bit of the Unity version, e.g. 0f1 + /// + public string Release; + + public override string ToString() + { + if (string.IsNullOrEmpty(Version)) + return Release; + + if (string.IsNullOrEmpty(Release)) + return Release; + + return $"{Version}.{Release}"; + } + } + + /// + /// Returns a package identifier, consisting of package name and package version + /// + /// + /// + public static string GetPackageIdentifier(this PackageInfo package) + { + return $"{package.name}-{package.version}"; + } + + public static PackageInfo[] GetAllPackages() + { +#if !UNITY_2021_1_OR_NEWER + var method = typeof(PackageInfo).GetMethod("GetAll", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, new Type[0], null); + var packages = method?.Invoke(null, null) as PackageInfo[]; +#else + var packages = PackageInfo.GetAllRegisteredPackages(); +#endif + return packages; + } + + public static PackageInfo[] GetAllLocalPackages() + { + var packages = GetAllPackages(); + var localPackages = packages.Where(x => x.source == PackageSource.Embedded || x.source == PackageSource.Local) + .Where(x => x.isDirectDependency).ToArray(); + return localPackages; + } + + public static PackageInfo[] GetAllRegistryPackages() + { + var packages = GetAllPackages(); + var registryPackages = packages.Where(x => x.source == PackageSource.Registry || x.source == PackageSource.BuiltIn) + .OrderBy(x => string.Compare(x.type, "module", System.StringComparison.OrdinalIgnoreCase) == 0) + .ThenBy(x => x.name).ToArray(); + + return registryPackages; + } + + public static bool GetPackageByManifestPath(string packageManifestPath, out PackageInfo package) + { + package = null; + + if (string.IsNullOrEmpty(packageManifestPath)) + return false; + + var fileInfo = new FileInfo(packageManifestPath); + if (!fileInfo.Exists) + return false; + + var allPackages = GetAllPackages(); + + package = allPackages.FirstOrDefault(x => Path.GetFullPath(x.resolvedPath).Equals(fileInfo.Directory.FullName)); + return package != null; + } + + public static bool GetPackageByPackageName(string packageName, out PackageInfo package) + { + package = null; + + if (string.IsNullOrEmpty(packageName)) + return false; + + return GetPackageByManifestPath($"Packages/{packageName}/package.json", out package); + } + + public static TextAsset GetManifestAsset(this PackageInfo packageInfo) + { + return AssetDatabase.LoadAssetAtPath($"{packageInfo.assetPath}/package.json"); + } + + public static List GetSamples(this PackageInfo packageInfo) + { + var samples = new List(); + + var packageManifest = packageInfo.GetManifestAsset(); + var json = JObject.Parse(packageManifest.text); + + if (!json.ContainsKey("samples") || json["samples"].Type != JTokenType.Array) + return samples; + + var sampleList = json["samples"].ToList(); + foreach (JObject sample in sampleList) + { + var displayName = string.Empty; + var description = string.Empty; + var path = string.Empty; + + if (sample.ContainsKey("displayName")) + displayName = sample["displayName"].ToString(); + if (sample.ContainsKey("description")) + description = sample["description"].ToString(); + if (sample.ContainsKey("path")) + path = sample["path"].ToString(); + + if (!string.IsNullOrEmpty(displayName) || !string.IsNullOrEmpty(description) || !string.IsNullOrEmpty(path)) + samples.Add(new PackageInfoSampleMetadata() { DisplayName = displayName, Description = description, Path = path }); + } + + return samples; + } + + public static PackageInfoUnityVersionMetadata GetUnityVersion(this PackageInfo packageInfo) + { + var packageManifest = packageInfo.GetManifestAsset(); + var json = JObject.Parse(packageManifest.text); + + var unityVersion = string.Empty; + var unityRelease = string.Empty; + + if (json.ContainsKey("unity")) + unityVersion = json["unity"].ToString(); + if (json.ContainsKey("unityRelease")) + unityRelease = json["unityRelease"].ToString(); + + return new PackageInfoUnityVersionMetadata() + { + Version = unityVersion, + Release = unityRelease + }; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs.meta new file mode 100644 index 0000000..6ce8b3d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/PackageUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 605ea62f8b11d674a95a53f895df4c67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs new file mode 100644 index 0000000..919b849 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AssetStoreTools.Utility +{ + internal abstract class ServiceProvider + { + private Dictionary _services = new Dictionary(); + private Dictionary> _queuedServices = new Dictionary>(); + + protected class MissingServiceDependencyException : Exception + { + public Type ServiceType { get; private set; } + public Type MissingDependencyType { get; private set; } + + public MissingServiceDependencyException(Type serviceType, Type missingDependencyType) + { + ServiceType = serviceType; + MissingDependencyType = missingDependencyType; + } + } + + protected ServiceProvider() + { + RegisterServices(); + CreateRegisteredServices(); + } + + protected abstract void RegisterServices(); + + protected void Register() where TService : Service where TInstance : TService + { + Register(() => CreateServiceInstance(typeof(TInstance))); + } + + protected void Register(Func initializer) where TService : Service + { + _queuedServices.Add(typeof(TService), initializer); + } + + private void CreateRegisteredServices() + { + if (_queuedServices.Count == 0) + return; + + var createdAnyService = false; + var missingServices = new List(); + + foreach (var service in _queuedServices) + { + try + { + var instance = service.Value.Invoke(); + _services.Add(service.Key, instance); + createdAnyService = true; + } + catch (MissingServiceDependencyException e) + { + missingServices.Add(e); + } + } + + foreach (var createdService in _services) + { + _queuedServices.Remove(createdService.Key); + } + + if (!createdAnyService) + { + var message = string.Join(", ", missingServices.Select(x => $"{x.ServiceType} depends on {x.MissingDependencyType}")); + throw new Exception("Could not create the following services due to missing dependencies: " + message); + } + + // Recursively register remaining queued services that may have failed + // due to missing depenedencies that are now registered + CreateRegisteredServices(); + } + + private Service CreateServiceInstance(Type concreteType) + { + if (concreteType.IsAbstract) + throw new Exception($"Cannot create an instance of an abstract class {concreteType}"); + + var constructor = concreteType.GetConstructors().First(); + var expectedParameters = constructor.GetParameters(); + var parametersToUse = new List(); + + foreach (var parameter in expectedParameters) + { + if (!_services.ContainsKey(parameter.ParameterType)) + throw new MissingServiceDependencyException(concreteType, parameter.ParameterType); + + parametersToUse.Add(_services[parameter.ParameterType]); + } + + return (Service)constructor.Invoke(parametersToUse.ToArray()); + } + + public T GetService() where T : Service + { + return (T)GetService(typeof(T)); + } + + public object GetService(Type type) + { + if (!_services.ContainsKey(type)) + throw new Exception($"Service of type {type} is not registered"); + + return _services[type]; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs.meta new file mode 100644 index 0000000..847bd4b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/ServiceProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fcadafa6431d1647a82d35e6e4a13c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs new file mode 100644 index 0000000..faf7665 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs @@ -0,0 +1,55 @@ +using System; +using UnityEditor; +using UnityEngine.UIElements; +using WindowStyles = AssetStoreTools.Constants.WindowStyles; + +namespace AssetStoreTools.Utility +{ + internal static class StyleSelector + { + private static StyleSheet GetStylesheet(string rootPath, string filePath) + { + var path = $"{rootPath}/{filePath}.uss"; + var sheet = AssetDatabase.LoadAssetAtPath(path); + if (sheet == null) + throw new Exception($"Stylesheet '{path}' was not found"); + return sheet; + } + + private static StyleSheet GetStylesheetTheme(string rootPath, string filePath) + { + var suffix = !EditorGUIUtility.isProSkin ? "Light" : "Dark"; + return GetStylesheet(rootPath, filePath + suffix); + } + + public static class UploaderWindow + { + public static StyleSheet UploaderWindowStyle => GetStylesheet(WindowStyles.UploaderStylesPath, "Style"); + public static StyleSheet UploaderWindowTheme => GetStylesheetTheme(WindowStyles.UploaderStylesPath, "Theme"); + + public static StyleSheet LoginViewStyle => GetStylesheet(WindowStyles.UploaderStylesPath, "LoginView/Style"); + public static StyleSheet LoginViewTheme => GetStylesheetTheme(WindowStyles.UploaderStylesPath, "LoginView/Theme"); + + public static StyleSheet PackageListViewStyle => GetStylesheet(WindowStyles.UploaderStylesPath, "PackageListView/Style"); + public static StyleSheet PackageListViewTheme => GetStylesheetTheme(WindowStyles.UploaderStylesPath, "PackageListView/Theme"); + } + + public static class ValidatorWindow + { + public static StyleSheet ValidatorWindowStyle => GetStylesheet(WindowStyles.ValidatorStylesPath, "Style"); + public static StyleSheet ValidatorWindowTheme => GetStylesheetTheme(WindowStyles.ValidatorStylesPath, "Theme"); + } + + public static class PreviewGeneratorWindow + { + public static StyleSheet PreviewGeneratorWindowStyle => GetStylesheet(WindowStyles.PreviewGeneratorStylesPath, "Style"); + public static StyleSheet PreviewGeneratorWindowTheme => GetStylesheetTheme(WindowStyles.PreviewGeneratorStylesPath, "Theme"); + } + + public static class UpdaterWindow + { + public static StyleSheet UpdaterWindowStyle => GetStylesheet(WindowStyles.UpdaterStylesPath, "Style"); + public static StyleSheet UpdaterWindowTheme => GetStylesheetTheme(WindowStyles.UpdaterStylesPath, "Theme"); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs.meta new file mode 100644 index 0000000..d4ab01c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/StyleSelector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b066ce502a289a4ca311a86fbf83f45 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles.meta new file mode 100644 index 0000000..0175b81 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f730eb0b8c48c434d93cc60a0b8aff40 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater.meta new file mode 100644 index 0000000..315b99b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5367435d9abe935438f4d7b588a55488 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss new file mode 100644 index 0000000..afcdd3e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss @@ -0,0 +1,76 @@ +.updater-loading-container { + flex-grow: 1; + align-items: center; + justify-content: center; +} + +.updater-loading-container > Image { + width: 16px; + height: 16px; +} + +.updater-info-container { + flex-grow: 1; + margin: 0 5px 5px 5px; +} + +.updater-info-container-labels { + flex-grow: 1; + margin-bottom: 10px; + margin-top: 5px; +} + +.updater-info-container-labels-description { + flex-grow: 0.5; + margin-bottom: 5px; + white-space: normal; + -unity-text-align: middle-left; +} + +.updater-info-container-labels-row { + flex-direction: row; +} + +.updater-info-container-labels-row-identifier { + -unity-font-style: bold; +} + +.updater-info-container-buttons { + flex-direction: row; + margin-bottom: 5px; +} + +.updater-info-container-buttons > Button { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 100%; + height: 25px; +} + +.updater-info-container-toggle { + align-self: flex-end; +} + +.updater-info-container-toggle > Toggle > VisualElement > Label { + margin-left: 5px; +} + +.updater-fail-container { + flex-grow: 1; + flex-direction: row; + margin: 0 5px 5px 5px; + justify-content: center; + align-items: center; +} + +.updater-fail-container > Image { + flex-shrink: 0; + width: 36px; + height: 36px; + margin-right: 5px; +} + +.updater-fail-container > Label { + flex-shrink: 1; + white-space: normal; +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss.meta new file mode 100644 index 0000000..3ba3542 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/Style.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23112eed1f211274c94028490f81007c +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss new file mode 100644 index 0000000..a0f11f8 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss @@ -0,0 +1,3 @@ +.updater-fail-container > Image { + --unity-image: resource("console.erroricon@2x"); +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss.meta new file mode 100644 index 0000000..9fad31b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeDark.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cbf43b8dabcd1242b32ed3ed2167a54 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss new file mode 100644 index 0000000..a0f11f8 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss @@ -0,0 +1,3 @@ +.updater-fail-container > Image { + --unity-image: resource("console.erroricon@2x"); +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss.meta new file mode 100644 index 0000000..0cdf6fd --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/Styles/Updater/ThemeLight.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d453bb92cd1f35943b1c5f652837ada9 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs new file mode 100644 index 0000000..fb2d435 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs @@ -0,0 +1,67 @@ +using System.IO; + +namespace AssetStoreTools.Utility +{ + internal static class SymlinkUtil + { + private const FileAttributes FolderSymlinkAttributes = FileAttributes.Directory | FileAttributes.ReparsePoint; + + public static bool FindSymlinkFolderRelative(string folderPathAbsolute, out string relativePath) + { + // Get directory info for path outside of the project + var absoluteInfo = new DirectoryInfo(folderPathAbsolute); + + // Get all directories within the project + var allFolderPaths = Directory.GetDirectories("Assets", "*", SearchOption.AllDirectories); + foreach (var path in allFolderPaths) + { + var fullPath = path.Replace("\\", "/"); + + // Get directory info for one of the paths within the project + var relativeInfo = new DirectoryInfo(fullPath); + + // Check if project's directory is a symlink + if (!relativeInfo.Attributes.HasFlag(FolderSymlinkAttributes)) + continue; + + // Compare metadata of outside directory with a directories within the project + if (!CompareDirectories(absoluteInfo, relativeInfo)) + continue; + + // Found symlink within the project, assign it + relativePath = fullPath; + return true; + } + + relativePath = string.Empty; + return false; + } + + private static bool CompareDirectories(DirectoryInfo directory, DirectoryInfo directory2) + { + var contents = directory.EnumerateFileSystemInfos("*", SearchOption.AllDirectories).GetEnumerator(); + var contents2 = directory2.EnumerateFileSystemInfos("*", SearchOption.AllDirectories).GetEnumerator(); + + while (true) + { + var firstNext = contents.MoveNext(); + var secondNext = contents2.MoveNext(); + + if (firstNext != secondNext) + return false; + + if (!firstNext && !secondNext) + break; + + var equals = contents.Current?.Name == contents2.Current?.Name + && contents.Current?.LastWriteTime == contents2.Current?.LastWriteTime; + + if (!equals) + return false; + } + + return true; + } + + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs.meta new file mode 100644 index 0000000..43c8aaf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Utility/SymlinkUtil.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 92092535fd064bb1843017f98db213e1 +timeCreated: 1659013521 \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator.meta new file mode 100644 index 0000000..e27c1ef --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 980c7bb65c02d464684c2220c57fcd75 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons.meta new file mode 100644 index 0000000..7f4c321 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8490c57c02b441e4dab99565da835c99 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png new file mode 100644 index 0000000000000000000000000000000000000000..8d294bc199d18c75542a22f1a5872e3c9276b863 GIT binary patch literal 1057 zcmV++1m63JP)@~0drDELIAGL9O(c600d`2O+f$vv5yP|(KsZ6^2^#hdO&5KnE9eQDBXEOeMR{%G zspMeu0=BHg(nxvv-U~RfVtf9~NFI5FUUvkOEga=U@SZ znozvE32Pf?@=))96QVOnexKstdJHwFB|vJtXkN*qw7>^MBPqgO9T655z`ODJh13&a zApwFhKBI;Ra|;lh@fiz>FqZ(q8lSO%2*v^gZ+ym_A{Ys*$#KcCtbW4o)+#pXrBOz zF4&~P6AA@z_{-nm#pSlP$>Hl?)^iDs2v0)5afdgkY(bVoL7@qU83Ek)_GmCPQLW^r z{M5xFG5Ypn0mlkv&(XmV`Lmg;2I^uTJwY2 z6m+oPk^kQV2Lus>Lx+yqyF#kLIFn-%-xL8CgeNKl`0nq~$OBB;SOQzZ-2`Sax?59< zW7cbtYzp8;G+&hOyyF%C_ytJ*Zk6t9G$LSzUjP&@As{@2K%?BvlOzIW_yr*CFvsp4 z`~ql49bADXj&!x z+e{okt9(>1G*cv%=H%j!yFv+)?w#DeDy!I-17j!;G5STvQND>M-}m(Atp z3`GPV3n%k)`xe2%(%D*bf)BX^o&h^audeZm+N;Mirt^8K?iiIfxXS6rY8nd?=;D{l bA7Fw1SKskfA=so700000NkvXXu0mjfJ)q0> literal 0 HcmV?d00001 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png.meta new file mode 100644 index 0000000..4217052 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 0cc0ccdb7de3e964ab553ce3c299d83c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 0 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 0 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error_d.png b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/error_d.png new file mode 100644 index 0000000000000000000000000000000000000000..451f64037adef0edb0178cc7a335c513faa0f444 GIT binary patch literal 1024 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 literal 0 HcmV?d00001 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success.png.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success.png.meta new file mode 100644 index 0000000..c08b383 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 832e106a677623145b3d8dbe015e31a0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 0 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 0 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png new file mode 100644 index 0000000000000000000000000000000000000000..094a810f9ec5788ff713a0780fd3010c81151c37 GIT binary patch literal 1617 zcmV-X2Cn&uP)@~0drDELIAGL9O(c600d`2O+f$vv5yPJwrV6PtsY1GBCgamFnMqO^cj*jON|Q1bQs)l2 z^8g8#KfsJ!2GDKZ+eMLN-Rbu3_LLL;%m^LQo}_*E_U-$10%hZ`N%~ud=&r*P5(@Oc zm;;m?9w!x?5QZobfim#dc*2h%yzSA6W+sGEO{m%-g4azr#+?9s zIR|fcmyW)n#~jKbq(=kMRm+uT6c@l`CjKrR(uxD9>t{s;Q9}{-R(Bz4FX-qIJ?>$0 z+=CFG(syrT>wX8#H?NSBd>X)p%~Q{5eb-S9$Z%e5<6wUI4+$0e#6=s6KT(T#z`v{M}}- zoec<1 z8j&mjw)VhJn0DA-*qncxv^&Jx0KLygwC*S7*_8|hS<&@_I6ooY26)ji?#$#Fiax!W zSbH$K1pMV4QkxyzIfRT`3d2jls6JnPi93m23X8N9I=xqhmw?x0!zWR7I6nZfs~-Xf z_9xO(5VJ$OT3-XE#_)A_;Qx6z^pd~4LD?otyEUu@0~*k+qw14aQDbECOmNnQhyqwK ztOb1mc)~}u3DG!Do@Dpt0GueXL8#r{p%DP*@T$J{Io=HX*k8$-6yKqPjG!u6`}bYQrRcT>O|c zAgXSt%b8#Q^e}ZCd#?&R$Vzvn?vW?3j%q@w@9XrfP5Uo7cQBYpyAjxvGU1sw1Jio#cH*hderx3ml8Jwuns;$SGFn(M%cUQs{3LQ`P*%JDxUqg0iyRj02daVX@Mr65X_!mUXq4`t^wa;I2v@T1xz*}l;iFWGE!q0 z765C3b&=5;J0XRc`bhJ-M4 zLe9-_9gCee^#a+bd&y*<8Dlshc9?sJxQ3R`7i++F|t+p$AnL$JcVL+@#`G?SFgsVe%) zr%#@-zh9vF=IAz&QAd^GZW90eYun-3?ik^0&cpe)Bn38%Rl941t5V;MZKR69&IY_o z)jCS`jg^#K&Cy4cPXImGcQ@{<#Q#G__Yv5X(i13l-(c&qjm?iJn-sBa;Je5swJa!d zjee%9{y7?dwNQX!8Zd}XVRh>zYeYvIIgaQC8lH>bP5^@hOPl&WKGS|DQ4GF$`iQo+ zqh1=zCFKZU5ObI5IyWT2I8%~4GcYQckZX1N8C0#Jq6KQ#P5f~2H%jDRfoMCn-b@k~ P00000NkvXXu0mjfDn|lL literal 0 HcmV?d00001 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png.meta new file mode 100644 index 0000000..6741130 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/success_d.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 3dc139a2b2a28a54a8f39e266fc0af9c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 0 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 0 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined.png b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/undefined.png new file mode 100644 index 0000000000000000000000000000000000000000..a587baa45ab11888902d103b690a2f579aeb55c6 GIT binary patch literal 1561 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 Hu0mjf^t1iH literal 0 HcmV?d00001 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning.png.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning.png.meta new file mode 100644 index 0000000..05dd39d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning.png.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 83d0e58aa5f608a4b8232fbacca5ca89 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 0 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 0 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 0 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: iPhone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning_d.png b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Icons/warning_d.png new file mode 100644 index 0000000000000000000000000000000000000000..553bb16d81ff583b4f190471c02e74418503bd29 GIT binary patch literal 1185 zcmV;S1YY}zP)@~0drDELIAGL9O(c600d`2O+f$vv5yP_5f**n>aBm==3+_%^ zy}N_MGSk!3UDd1d63Aw{hoN4*s;-*uRfvq!JuF=I@sd8S(NAiwDd0>ibTf^e2OH=i zA`~Lx%UiTt3P<$Rg^DS?L54>UpWzIV5RHK2U8IS|sm1s)G|mSaE3rsO5DE04qgvw) zXwF=MZA3y80?7O8n8dpaG|)MIgAO7f3IX(xS7?Ykt>*XmK%9W4-p>(B1>ywwct1xB z6^Ihx>-`)tR3J(~i1%|yt3Zr^Q19oER)H7+9Ny0%sR9uKxV)c3QUxstpw|0T?o#vf z3E8;ZO%k-p$aUK-6BTs94EA2Yc%KN^vrOq_z{+&jH|iBkxolBRHlX{S^OC?G0(IUm zGv2^lGh?=UC2|2P0d=uYGT*oL`=c=v8rAo+=K@v&YGZ#`ZCs9Qh(p^rw+h$@aLN2C zacGBYz^wu{0=U+FS`qHm&EZr53jxmSzH*vVs}fEXun^$teeFP!)^1_KrGj7rxU0Uz z@o#adAeexLv7b83aZZ#RDhMK=srQvj2s9voLj^$uaCUqJq)jG=s=%87&)83GgJ#zV z4H2pWZvuS1|LtS-I^Xa66-ps0@FIXa^B0i910gE#A|NF82ba}GCfdJL_EkY+0vO)( zrd4BE00F)#XiR`m<{$rUhd#gt9~CqrfF<^A{eTMV7JXFEhyV`n+d@F=@OHVW3Y-ZL z%=}kKX0VUoOaNo-r8U#?Mg38U&lU@9Pp#04?nI7OExq}6z@WT9~5CR$5UD_z$cPu5qVTbph zL)Ff5X$VAI_oJaI7u--l6z^-C)01B(YJ5NjPK9g#pdE; zBH<4XiTE8_3ASm%DuhsW%H)~?B6z>Iyn*{wgQgn%)yEMf#5RO5t%7SgK-~MAD~(e2~ukJ(o#Sxj_U)6^saoRP(1U9iKTZ z24NIckW79>S@$n4oXXt}A|O|Ridyy2`kyH*R-F`cIV;Esvm#I_F4KgRf?j(Y1IwBd zGQJbjcmtyXb@C2BL*K{yzUfwJu_^lvk{MLsPYDX^00000NkvXXu0mjf String.Compare(x, category, StringComparison.OrdinalIgnoreCase) == 0); + + if (IsInclusiveFilter) + { + if (isCategoryInFilter) + return IsFailFilter ? TestResultStatus.Fail : TestResultStatus.Warning; + else + return IsFailFilter ? TestResultStatus.Warning : TestResultStatus.Fail; + } + else + { + if (isCategoryInFilter) + return IsFailFilter ? TestResultStatus.Warning : TestResultStatus.Fail; + else + return IsFailFilter ? TestResultStatus.Fail : TestResultStatus.Warning; + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/ValidatorCategory.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/ValidatorCategory.cs.meta new file mode 100644 index 0000000..2690769 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Categories/ValidatorCategory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a5e60d3639f24063a4eabc21ea1a04a9 +timeCreated: 1657617578 \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs new file mode 100644 index 0000000..1fbc6ba --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs @@ -0,0 +1,71 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.IO; + +namespace AssetStoreTools.Validator +{ + internal class CurrentProjectValidator : ValidatorBase + { + private CurrentProjectValidationSettings _settings; + + public CurrentProjectValidator(CurrentProjectValidationSettings settings) : base(settings) + { + _settings = settings; + } + + protected override void ValidateSettings() + { + if (_settings == null) + throw new Exception("Validation Settings is null"); + + if (_settings.ValidationPaths == null + || _settings.ValidationPaths.Count == 0) + throw new Exception("No validation paths were set"); + + switch (_settings.ValidationType) + { + case ValidationType.Generic: + case ValidationType.UnityPackage: + ValidateUnityPackageSettings(); + break; + default: + throw new NotImplementedException("Undefined validation type"); + } + } + + private void ValidateUnityPackageSettings() + { + var invalidPaths = string.Empty; + foreach (var path in _settings.ValidationPaths) + { + if (!Directory.Exists(path)) + invalidPaths += $"\n{path}"; + } + + if (!string.IsNullOrEmpty(invalidPaths)) + throw new Exception("The following directories do not exist:" + invalidPaths); + } + + protected override ValidationResult GenerateValidationResult() + { + ITestConfig config; + var applicableTests = GetApplicableTests(ValidationType.Generic); + switch (_settings.ValidationType) + { + case ValidationType.Generic: + config = new GenericTestConfig() { ValidationPaths = _settings.ValidationPaths.ToArray() }; + break; + case ValidationType.UnityPackage: + applicableTests.AddRange(GetApplicableTests(ValidationType.UnityPackage)); + config = new GenericTestConfig() { ValidationPaths = _settings.ValidationPaths.ToArray() }; + break; + default: + return new ValidationResult() { Status = ValidationStatus.Failed, Exception = new Exception("Undefined validation type") }; + } + + var validationResult = RunTests(applicableTests, config); + return validationResult; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs.meta new file mode 100644 index 0000000..ca8fe3d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/CurrentProjectValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a6371dcfa8545c478545b4a43433599 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data.meta new file mode 100644 index 0000000..3b40063 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c2a38ded8e054c4088aff1db7224f66 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs new file mode 100644 index 0000000..446c73c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Linq; + +namespace AssetStoreTools.Validator.Data +{ + internal class CurrentProjectValidationSettings : ValidationSettings + { + public List ValidationPaths; + public ValidationType ValidationType; + + public CurrentProjectValidationSettings() + { + Category = string.Empty; + ValidationPaths = new List(); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != typeof(CurrentProjectValidationSettings)) + return false; + + var other = (CurrentProjectValidationSettings)obj; + return Category == other.Category + && ValidationType == other.ValidationType + && ValidationPaths.OrderBy(x => x).SequenceEqual(other.ValidationPaths.OrderBy(x => x)); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs.meta new file mode 100644 index 0000000..cf2630a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/CurrentProjectValidationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e4a4a4aa3f501847b1abb1e08505f9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs new file mode 100644 index 0000000..f4a85f3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs @@ -0,0 +1,7 @@ +namespace AssetStoreTools.Validator.Data +{ + internal class ExternalProjectValidationSettings : ValidationSettings + { + public string PackagePath; + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs.meta new file mode 100644 index 0000000..a7a30e0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ExternalProjectValidationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f79c895f4bb099b4983dd20eef72a7bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions.meta new file mode 100644 index 0000000..7f0bd9a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d51c5c866dcd449488caa10a40dd3301 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs new file mode 100644 index 0000000..932b607 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.Data.MessageActions +{ + internal class HighlightObjectAction : IMessageAction + { + public string Tooltip => "Click to highlight the associated object in Hierarchy/Project view"; + public Object Target => _target?.GetObject(); + + [JsonProperty] + private TestResultObject _target; + + public HighlightObjectAction() { } + + public HighlightObjectAction(Object target) + { + _target = new TestResultObject(target); + } + + public void Execute() + { + var targetObject = _target.GetObject(); + if (targetObject == null) + return; + + EditorGUIUtility.PingObject(targetObject); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs.meta new file mode 100644 index 0000000..f9d8e9c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/HighlightObjectAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de24c0a7f8a22c142a224e6abd0ddc68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs new file mode 100644 index 0000000..4e5e887 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using UnityEngine; + +namespace AssetStoreTools.Validator.Data.MessageActions +{ + internal interface IMessageAction + { + [JsonIgnore] + string Tooltip { get; } + + [JsonIgnore] + Object Target { get; } + + void Execute(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs.meta new file mode 100644 index 0000000..db2e565 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/IMessageAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f1636d7241abdf1498368f841aa818a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs new file mode 100644 index 0000000..c505aac --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.Data.MessageActions +{ + internal class OpenAssetAction : IMessageAction + { + public string Tooltip => "Click to open the associated asset"; + public Object Target => _target?.GetObject(); + + [JsonProperty] + private TestResultObject _target; + [JsonProperty] + private int _lineNumber; + + public OpenAssetAction() { } + + public OpenAssetAction(Object target) + { + _target = new TestResultObject(target); + } + + public OpenAssetAction(Object target, int lineNumber) : this(target) + { + _lineNumber = lineNumber; + } + + public void Execute() + { + var targetObject = _target.GetObject(); + if (targetObject == null) + return; + + AssetDatabase.OpenAsset(targetObject, _lineNumber); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs.meta new file mode 100644 index 0000000..83519a6 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/MessageActions/OpenAssetAction.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9fb4fec293bf73f4a8f870c535750613 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs new file mode 100644 index 0000000..6f40c5d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs @@ -0,0 +1,52 @@ +using AssetStoreTools.Validator.Data.MessageActions; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.Data +{ + internal struct TestResult + { + public TestResultStatus Status; + + [JsonProperty] + private List _messages; + + [JsonIgnore] + public int MessageCount => _messages != null ? _messages.Count : 0; + + public TestResultMessage GetMessage(int index) + { + return _messages[index]; + } + + public void AddMessage(string msg) + { + AddMessage(msg, null, null); + } + + public void AddMessage(string msg, IMessageAction clickAction) + { + AddMessage(msg, clickAction, null); + } + + public void AddMessage(string msg, IMessageAction clickAction, params UnityEngine.Object[] messageObjects) + { + if (_messages == null) + _messages = new List(); + + var message = new TestResultMessage(msg, clickAction); + _messages.Add(message); + + if (messageObjects == null) + return; + + foreach (var obj in messageObjects) + { + if (obj == null) + continue; + + message.AddMessageObject(obj); + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs.meta new file mode 100644 index 0000000..ec1467f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05d7d92bbda6bf44f8ed5fbd0cde57e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs new file mode 100644 index 0000000..d524cb0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs @@ -0,0 +1,53 @@ +using AssetStoreTools.Validator.Data.MessageActions; +using Newtonsoft.Json; +using System.Collections.Generic; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Data +{ + internal class TestResultMessage + { + [JsonIgnore] + public int MessageObjectCount => _messageObjects.Count; + + [JsonProperty] + private string _text; + [JsonProperty] + private List _messageObjects; + [JsonProperty] + private IMessageAction _clickAction; + + public TestResultMessage() { } + + public TestResultMessage(string text) + { + _text = text; + _messageObjects = new List(); + } + + public TestResultMessage(string text, IMessageAction clickAction) : this(text) + { + _clickAction = clickAction; + } + + public string GetText() + { + return _text; + } + + public IMessageAction GetClickAction() + { + return _clickAction; + } + + public void AddMessageObject(Object obj) + { + _messageObjects.Add(new TestResultObject(obj)); + } + + public TestResultObject GetMessageObject(int index) + { + return _messageObjects[index]; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs.meta new file mode 100644 index 0000000..2f0262f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0761356c44140ca49917f93b42926471 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs new file mode 100644 index 0000000..8fa7019 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.Data +{ + internal class TestResultObject + { + [JsonIgnore] + private Object _object; + [JsonProperty] + private string _objectGlobalId; + + public TestResultObject(Object obj) + { + _object = obj; + _objectGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(obj).ToString(); + } + + public Object GetObject() + { + if (_object != null) + return _object; + + if (string.IsNullOrEmpty(_objectGlobalId)) + return null; + + if (!GlobalObjectId.TryParse(_objectGlobalId, out var globalObject)) + return null; + + _object = GlobalObjectId.GlobalObjectIdentifierToObjectSlow(globalObject); + return _object; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs.meta new file mode 100644 index 0000000..a688e88 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acce8e477b7fe2c4aa430ebdd65ea7d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs new file mode 100644 index 0000000..e304859 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs @@ -0,0 +1,11 @@ +namespace AssetStoreTools.Validator.Data +{ + internal enum TestResultStatus + { + Undefined = 0, + Pass = 1, + Fail = 2, + Warning = 3, + VariableSeverityIssue = 4 + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs.meta new file mode 100644 index 0000000..7e7401a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/TestResultStatus.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eef1ba0cf35f1304d8929e23b94e7c23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs new file mode 100644 index 0000000..2ee0c43 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs @@ -0,0 +1,24 @@ +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.Data +{ + internal class ValidationResult + { + public ValidationStatus Status; + public bool HadCompilationErrors; + public string ProjectPath; + public List Tests; + public Exception Exception; + + public ValidationResult() + { + Status = ValidationStatus.NotRun; + HadCompilationErrors = false; + ProjectPath = string.Empty; + Tests = new List(); + Exception = null; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs.meta new file mode 100644 index 0000000..da48108 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b15525b8dcf3e654ca2f895472ab7cb1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs new file mode 100644 index 0000000..8747266 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs @@ -0,0 +1,7 @@ +namespace AssetStoreTools.Validator.Data +{ + internal abstract class ValidationSettings + { + public string Category; + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs.meta new file mode 100644 index 0000000..7783188 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33e99d6b6e1e7ef4abd6cd2c0137741a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs new file mode 100644 index 0000000..385bb92 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs @@ -0,0 +1,10 @@ +namespace AssetStoreTools.Validator.Data +{ + internal enum ValidationStatus + { + NotRun, + RanToCompletion, + Failed, + Cancelled + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs.meta new file mode 100644 index 0000000..32b6657 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationStatus.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1f1e1e94faa6284f8d71804ba2bbd24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs new file mode 100644 index 0000000..964602c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs @@ -0,0 +1,8 @@ +namespace AssetStoreTools.Validator.Data +{ + internal enum ValidationType + { + Generic = 0, + UnityPackage = 1 + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs.meta new file mode 100644 index 0000000..dca424c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Data/ValidationType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 079f8963464230145853d86eff935e04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs new file mode 100644 index 0000000..c38c51d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs @@ -0,0 +1,259 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.Data; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator +{ + internal class ExternalProjectValidator : ValidatorBase + { + private ExternalProjectValidationSettings _settings; + + public ExternalProjectValidator(ExternalProjectValidationSettings settings) : base(settings) + { + _settings = settings; + } + + protected override void ValidateSettings() + { + if (_settings == null) + throw new Exception("Validation Settings is null"); + + if (string.IsNullOrEmpty(_settings.PackagePath) + || !File.Exists(_settings.PackagePath)) + throw new Exception("Package was not found"); + } + + protected override ValidationResult GenerateValidationResult() + { + bool interactiveMode = false; + try + { + // Step 1 - prepare a temporary project + var result = PrepareTemporaryValidationProject(interactiveMode); + + // If preparation was cancelled or setting up project failed - return immediately + if (result.Status == ValidationStatus.Cancelled || result.Status == ValidationStatus.Failed) + return result; + + // Step 2 - load the temporary project and validate the package + result = ValidateTemporaryValidationProject(result, interactiveMode); + + // Step 3 - copy validation results + result = ParseValidationResult(result.ProjectPath); + + return result; + } + catch (Exception e) + { + return new ValidationResult() { Status = ValidationStatus.Failed, Exception = e }; + } + finally + { + EditorUtility.ClearProgressBar(); + } + } + + private ValidationResult PrepareTemporaryValidationProject(bool interactiveMode) + { + EditorUtility.DisplayProgressBar("Validating...", "Preparing the validation project. This may take a while.", 0.3f); + + var result = new ValidationResult(); + var tempProjectPath = Path.Combine(Constants.RootProjectPath, "Temp", GUID.Generate().ToString()).Replace("\\", "/"); + result.ProjectPath = tempProjectPath; + + if (!Directory.Exists(tempProjectPath)) + Directory.CreateDirectory(tempProjectPath); + + // Cannot edit a package.json file that does not yet exist - copy over AST instead + var tempPackagesPath = $"{tempProjectPath}/Packages"; + if (!Directory.Exists(tempPackagesPath)) + Directory.CreateDirectory(tempPackagesPath); + var assetStoreToolsPath = PackageUtility.GetAllPackages().FirstOrDefault(x => x.name == "com.unity.asset-store-tools").resolvedPath.Replace("\\", "/"); + FileUtility.CopyDirectory(assetStoreToolsPath, $"{tempPackagesPath}/com.unity.asset-store-tools", true); + + var logFilePath = $"{tempProjectPath}/preparation.log"; + + // Create the temporary project + var processInfo = new System.Diagnostics.ProcessStartInfo() + { + FileName = Constants.UnityPath, + Arguments = $"-createProject \"{tempProjectPath}\" -logFile \"{logFilePath}\" -importpackage \"{Path.GetFullPath(_settings.PackagePath)}\" -quit" + }; + + if (!interactiveMode) + processInfo.Arguments += " -batchmode"; + + var exitCode = 0; + + using (var process = System.Diagnostics.Process.Start(processInfo)) + { + while (!process.HasExited) + { + if (EditorUtility.DisplayCancelableProgressBar("Validating...", "Preparing the validation project. This may take a while.", 0.3f)) + process.Kill(); + + Thread.Sleep(10); + } + + exitCode = process.ExitCode; + + // Windows and MacOS exit codes + if (exitCode == -1 || exitCode == 137) + { + result.Status = ValidationStatus.Cancelled; + return result; + } + } + + if (exitCode != 0) + { + result.Status = ValidationStatus.Failed; + result.Exception = new Exception($"Setting up the temporary project failed (exit code {exitCode})\n\nMore information can be found in the log file: {logFilePath}"); + } + else + { + result.Status = ValidationStatus.RanToCompletion; + } + + return result; + } + + private ValidationResult ValidateTemporaryValidationProject(ValidationResult result, bool interactiveMode) + { + EditorUtility.DisplayProgressBar("Validating...", "Performing validation...", 0.6f); + + var logFilePath = $"{result.ProjectPath}/validation.log"; + var processInfo = new System.Diagnostics.ProcessStartInfo() + { + FileName = Constants.UnityPath, + Arguments = $"-projectPath \"{result.ProjectPath}\" -logFile \"{logFilePath}\" -executeMethod AssetStoreTools.Validator.ExternalProjectValidator.ValidateProject -category \"{_settings.Category}\"" + }; + + if (!interactiveMode) + processInfo.Arguments += " -batchmode -ignorecompilererrors"; + + var exitCode = 0; + + using (var process = System.Diagnostics.Process.Start(processInfo)) + { + process.WaitForExit(); + exitCode = process.ExitCode; + } + + if (exitCode != 0) + { + result.Status = ValidationStatus.Failed; + result.Exception = new Exception($"Validating the temporary project failed (exit code {exitCode})\n\nMore information can be found in the log file: {logFilePath}"); + } + else + { + result.Status = ValidationStatus.RanToCompletion; + } + + return result; + } + + private ValidationResult ParseValidationResult(string externalProjectPath) + { + if (!CachingService.GetCachedValidatorStateData(externalProjectPath, out var validationStateData)) + throw new Exception("Could not find external project's validation results"); + + var cachedResult = validationStateData.GetResults(); + var cachedTestResults = cachedResult.GetResults(); + var tests = GetApplicableTests(ValidationType.Generic, ValidationType.UnityPackage); + + foreach (var test in tests) + { + if (!cachedTestResults.Any(x => x.Key == test.Id)) + continue; + + var matchingTest = cachedTestResults.First(x => x.Key == test.Id); + test.Result = matchingTest.Value; + } + + var result = new ValidationResult() + { + Status = cachedResult.GetStatus(), + HadCompilationErrors = cachedResult.GetHadCompilationErrors(), + ProjectPath = cachedResult.GetProjectPath(), + Tests = tests + }; + + return result; + } + + public static void OpenExternalValidationProject(string projectPath) + { + var unityPath = Constants.UnityPath; + var logFilePath = $"{projectPath}/editor.log"; + + var processInfo = new System.Diagnostics.ProcessStartInfo() + { + FileName = unityPath, + Arguments = $"-projectPath \"{projectPath}\" -logFile \"{logFilePath}\" -executeMethod AssetStoreTools.AssetStoreTools.ShowAssetStoreToolsValidator" + }; + + using (var process = System.Diagnostics.Process.Start(processInfo)) + { + process.WaitForExit(); + } + } + + // Invoked via Command Line Arguments + private static void ValidateProject() + { + var exitCode = 0; + try + { + // Determine whether to validate Assets folder or Packages folders + var validationPaths = new List(); + var packageDirectories = Directory.GetDirectories("Packages", "*", SearchOption.TopDirectoryOnly) + .Select(x => x.Replace("\\", "/")) + .Where(x => x != "Packages/com.unity.asset-store-tools").ToArray(); + + if (packageDirectories.Length > 0) + validationPaths.AddRange(packageDirectories); + else + validationPaths.Add("Assets"); + + // Parse category + var category = string.Empty; + var args = Environment.GetCommandLineArgs().ToList(); + var categoryIndex = args.IndexOf("-category"); + if (categoryIndex != -1 && categoryIndex + 1 < args.Count) + category = args[categoryIndex + 1]; + + // Run validation + var validationSettings = new CurrentProjectValidationSettings() + { + Category = category, + ValidationPaths = validationPaths, + ValidationType = ValidationType.UnityPackage + }; + + var validator = new CurrentProjectValidator(validationSettings); + var result = validator.Validate(); + + // Display results + AssetStoreTools.ShowAssetStoreToolsValidator(validationSettings, result); + EditorUtility.DisplayDialog("Validation complete", "Package validation complete.\n\nTo resume work in the original project, close this Editor instance", "OK"); + } + catch + { + exitCode = 1; + throw; + } + finally + { + if (Application.isBatchMode) + EditorApplication.Exit(exitCode); + } + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs.meta new file mode 100644 index 0000000..46e89fb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ExternalProjectValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2664bbca63a2444498f13beb7e4fa731 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs new file mode 100644 index 0000000..e6ebee2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs @@ -0,0 +1,11 @@ +using AssetStoreTools.Validator.Data; + +namespace AssetStoreTools.Validator +{ + internal interface IValidator + { + ValidationSettings Settings { get; } + + ValidationResult Validate(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs.meta new file mode 100644 index 0000000..5a4e713 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/IValidator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d49e9393288e0ed418c546e57c4cb425 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services.meta new file mode 100644 index 0000000..03e4234 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9315c4052243ab2488208604c11c53c7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService.meta new file mode 100644 index 0000000..5d9a1cb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0a52c1c4a2b7caa458af5b9a212b80a5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs new file mode 100644 index 0000000..3ee5afa --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs @@ -0,0 +1,55 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.UI.Data.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace AssetStoreTools.Validator.Services +{ + internal class CachingService : ICachingService + { + public bool GetCachedValidatorStateData(out ValidatorStateData stateData) + { + return GetCachedValidatorStateData(Constants.RootProjectPath, out stateData); + } + + public bool GetCachedValidatorStateData(string projectPath, out ValidatorStateData stateData) + { + stateData = null; + if (!CacheUtil.GetFileFromProjectPersistentCache(projectPath, Constants.Cache.ValidationResultFile, out var filePath)) + return false; + + try + { + var serializerSettings = new JsonSerializerSettings() + { + ContractResolver = ValidatorStateDataContractResolver.Instance, + TypeNameHandling = TypeNameHandling.Auto, + Converters = new List() { new StringEnumConverter() } + }; + + stateData = JsonConvert.DeserializeObject(File.ReadAllText(filePath, Encoding.UTF8), serializerSettings); + return true; + } + catch + { + return false; + } + } + + public void CacheValidatorStateData(ValidatorStateData stateData) + { + var serializerSettings = new JsonSerializerSettings() + { + ContractResolver = ValidatorStateDataContractResolver.Instance, + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.Auto, + Converters = new List() { new StringEnumConverter() } + }; + + CacheUtil.CreateFileInPersistentCache(Constants.Cache.ValidationResultFile, JsonConvert.SerializeObject(stateData, serializerSettings), true); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs.meta new file mode 100644 index 0000000..00ba6e2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/CachingService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2d545f659acb4343bf485ffb20ecf72 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs new file mode 100644 index 0000000..1ac8e0a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs @@ -0,0 +1,11 @@ +using AssetStoreTools.Validator.UI.Data.Serialization; + +namespace AssetStoreTools.Validator.Services +{ + internal interface ICachingService : IValidatorService + { + void CacheValidatorStateData(ValidatorStateData stateData); + bool GetCachedValidatorStateData(out ValidatorStateData stateData); + bool GetCachedValidatorStateData(string projectPath, out ValidatorStateData stateData); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs.meta new file mode 100644 index 0000000..4f5aef4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/ICachingService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8a3e36c133848447b043a91e709c63e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs new file mode 100644 index 0000000..1090401 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json.Serialization; + +namespace AssetStoreTools.Previews.Services +{ + internal class PreviewDatabaseContractResolver : DefaultContractResolver + { + private static PreviewDatabaseContractResolver _instance; + public static PreviewDatabaseContractResolver Instance => _instance ?? (_instance = new PreviewDatabaseContractResolver()); + + private NamingStrategy _namingStrategy; + + private PreviewDatabaseContractResolver() + { + _namingStrategy = new SnakeCaseNamingStrategy(); + } + + protected override string ResolvePropertyName(string propertyName) + { + var resolvedName = _namingStrategy.GetPropertyName(propertyName, false); + if (resolvedName.StartsWith("_")) + return resolvedName.Substring(1); + + return resolvedName; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs.meta new file mode 100644 index 0000000..799994b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/CachingService/PreviewDatabaseContractResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aee615e9aaf50fb4f989cd4698e8947e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs new file mode 100644 index 0000000..d76eeea --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs @@ -0,0 +1,4 @@ +namespace AssetStoreTools.Validator.Services +{ + internal interface IValidatorService { } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs.meta new file mode 100644 index 0000000..74923b3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/IValidatorService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 075953f4ab4a65d4fae6e891360df0d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation.meta new file mode 100644 index 0000000..bf10d70 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 184dcfbfe1d21454fa8cf49f1c637871 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions.meta new file mode 100644 index 0000000..d6c2cf0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ed0af5acc22551645ae4cb7d75bd1c36 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs new file mode 100644 index 0000000..f2432f6 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface IAssetUtilityService : IValidatorService + { + IEnumerable GetAssetPathsFromAssets(string[] searchPaths, AssetType type); + IEnumerable GetObjectsFromAssets(string[] searchPaths, AssetType type) where T : Object; + IEnumerable GetObjectsFromAssets(string[] searchPaths, AssetType type); + string ObjectToAssetPath(Object obj); + T AssetPathToObject(string assetPath) where T : Object; + Object AssetPathToObject(string assetPath); + AssetImporter GetAssetImporter(string assetPath); + AssetImporter GetAssetImporter(Object asset); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs.meta new file mode 100644 index 0000000..cb8e4df --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IAssetUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d28c5ea40f4c9954bae02804e416b898 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs new file mode 100644 index 0000000..86d5d6e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs @@ -0,0 +1,7 @@ +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface IFileSignatureUtilityService : IValidatorService + { + ArchiveType GetArchiveType(string filePath); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs.meta new file mode 100644 index 0000000..132acba --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IFileSignatureUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 609c423482ecf8844a71166b4ef49cb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs new file mode 100644 index 0000000..2f16830 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface IMeshUtilityService : IValidatorService + { + IEnumerable GetCustomMeshesInObject(GameObject obj); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs.meta new file mode 100644 index 0000000..952889e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IMeshUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: acde6f9b97c9cac4b88a84aa9001a0fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs new file mode 100644 index 0000000..ed47ec4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface IModelUtilityService : IValidatorService + { + Dictionary> GetImportLogs(params Object[] models); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs.meta new file mode 100644 index 0000000..5e464c9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IModelUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91f6bacccdfecb84fb5ab0ba384353b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs new file mode 100644 index 0000000..d6d38f5 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs @@ -0,0 +1,13 @@ +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface ISceneUtilityService : IValidatorService + { + string CurrentScenePath { get; } + + Scene OpenScene(string scenePath); + GameObject[] GetRootGameObjects(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs.meta new file mode 100644 index 0000000..db0e94f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/ISceneUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cf5ef331063e5aa4e95dfe3eadedf9af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs new file mode 100644 index 0000000..e442aa0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal interface IScriptUtilityService : IValidatorService + { + IReadOnlyDictionary> GetTypeNamespacesFromScriptAssets(IList monoScripts); + IReadOnlyDictionary> GetTypesFromAssemblies(IList assemblies); + IReadOnlyDictionary> GetTypesFromScriptAssets(IList monoScripts); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs.meta new file mode 100644 index 0000000..e5458be --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Abstractions/IScriptUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0a9f88d37222e4428853b6d3d00b1bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs new file mode 100644 index 0000000..bacedaf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs @@ -0,0 +1,216 @@ +using AssetStoreTools.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class AssetUtilityService : IAssetUtilityService + { + public IEnumerable GetAssetPathsFromAssets(string[] searchPaths, AssetType type) + { + string filter = string.Empty; + string[] extensions = null; + + switch (type) + { + // General Types + case AssetType.All: + filter = ""; + break; + case AssetType.Prefab: + filter = "t:prefab"; + break; + case AssetType.Material: + filter = "t:material"; + break; + case AssetType.Model: + filter = "t:model"; + break; + case AssetType.Scene: + filter = "t:scene"; + break; + case AssetType.Texture: + filter = "t:texture"; + break; + case AssetType.Video: + filter = "t:VideoClip"; + break; + // Specific Types + case AssetType.LossyAudio: + filter = "t:AudioClip"; + extensions = new[] { ".mp3", ".ogg" }; + break; + case AssetType.NonLossyAudio: + filter = "t:AudioClip"; + extensions = new[] { ".wav", ".aif", ".aiff" }; + break; + case AssetType.JavaScript: + filter = "t:TextAsset"; + extensions = new[] { ".js" }; + break; + case AssetType.Mixamo: + filter = "t:model"; + extensions = new[] { ".fbx" }; + break; + case AssetType.JPG: + filter = "t:texture"; + extensions = new[] { ".jpg", "jpeg" }; + break; + case AssetType.Executable: + filter = string.Empty; + extensions = new[] { ".exe", ".bat", ".msi", ".apk" }; + break; + case AssetType.Documentation: + filter = string.Empty; + extensions = new[] { ".txt", ".pdf", ".html", ".rtf", ".md" }; + break; + case AssetType.SpeedTree: + filter = string.Empty; + extensions = new[] { ".spm", ".srt", ".stm", ".scs", ".sfc", ".sme", ".st" }; + break; + case AssetType.Shader: + filter = string.Empty; + extensions = new[] { ".shader", ".shadergraph", ".raytrace", ".compute" }; + break; + case AssetType.MonoScript: + filter = "t:script"; + extensions = new[] { ".cs" }; + break; + case AssetType.UnityPackage: + filter = string.Empty; + extensions = new[] { ".unitypackage" }; + break; + case AssetType.PrecompiledAssembly: + var assemblyPaths = GetPrecompiledAssemblies(searchPaths); + return assemblyPaths; + default: + return Array.Empty(); + } + + var guids = AssetDatabase.FindAssets(filter, searchPaths); + var paths = guids.Select(AssetDatabase.GUIDToAssetPath); + + if (extensions != null) + paths = paths.Where(x => extensions.Any(x.ToLower().EndsWith)); + + if (type == AssetType.Mixamo) + paths = paths.Where(IsMixamoFbx); + + paths = paths.Distinct(); + return paths; + } + + public IEnumerable GetObjectsFromAssets(string[] searchPaths, AssetType type) where T : Object + { + var paths = GetAssetPathsFromAssets(searchPaths, type); +#if !AB_BUILDER + var objects = paths.Select(AssetDatabase.LoadAssetAtPath).Where(x => x != null); +#else + var objects = new AssetEnumerator(paths); +#endif + return objects; + } + + public IEnumerable GetObjectsFromAssets(string[] searchPaths, AssetType type) + { + return GetObjectsFromAssets(searchPaths, type); + } + + private IEnumerable GetPrecompiledAssemblies(string[] searchPaths) + { + // Note - for packages, Compilation Pipeline returns full paths, as they appear on disk, not Asset Database + var allDllPaths = CompilationPipeline.GetPrecompiledAssemblyPaths(CompilationPipeline.PrecompiledAssemblySources.UserAssembly); + var rootProjectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "Assets".Length); + var packages = PackageUtility.GetAllLocalPackages(); + + var result = new List(); + foreach (var dllPath in allDllPaths) + { + var absoluteDllPath = Path.GetFullPath(dllPath).Replace("\\", "/"); + foreach (var validationPath in searchPaths) + { + var absoluteValidationPath = Path.GetFullPath(validationPath).Replace("\\", "/"); + if (absoluteDllPath.StartsWith(absoluteValidationPath)) + { + int pathSeparatorLength = 1; + if (absoluteDllPath.StartsWith(Application.dataPath)) + { + var adbPath = $"Assets/{absoluteDllPath.Remove(0, Application.dataPath.Length + pathSeparatorLength)}"; + result.Add(adbPath); + } + else + { + // For non-Asset folder paths (i.e. local and embedded packages), convert disk path to ADB path + var package = packages.FirstOrDefault(x => dllPath.StartsWith(x.resolvedPath.Replace('\\', '/'))); + + if (package == null) + continue; + + var dllPathInPackage = absoluteDllPath.Remove(0, Path.GetFullPath(package.resolvedPath).Length + pathSeparatorLength); + var adbPath = $"Packages/{package.name}/{dllPathInPackage}"; + + result.Add(adbPath); + } + } + } + } + + return result; + } + + private bool IsMixamoFbx(string fbxPath) + { + // Location of Mixamo Header, this is located in every mixamo fbx file exported + //const int mixamoHeader = 0x4c0 + 2; // < this is the original location from A$ Tools, unsure if Mixamo file headers were changed since then + const int mixamoHeader = 1622; + // Length of Mixamo header + const int length = 0xa; + + var fs = new FileStream(fbxPath, FileMode.Open); + // Check if length is further than + if (fs.Length < mixamoHeader) + return false; + + byte[] buffer = new byte[length]; + using (BinaryReader reader = new BinaryReader(fs)) + { + reader.BaseStream.Seek(mixamoHeader, SeekOrigin.Begin); + reader.Read(buffer, 0, length); + } + + string result = System.Text.Encoding.ASCII.GetString(buffer); + return result.Contains("Mixamo"); + } + + public string ObjectToAssetPath(Object obj) + { + return AssetDatabase.GetAssetPath(obj); + } + + public T AssetPathToObject(string assetPath) where T : Object + { + return AssetDatabase.LoadAssetAtPath(assetPath); + } + + public Object AssetPathToObject(string assetPath) + { + return AssetPathToObject(assetPath); + } + + public AssetImporter GetAssetImporter(string assetPath) + { + return AssetImporter.GetAtPath(assetPath); + } + + public AssetImporter GetAssetImporter(Object asset) + { + return GetAssetImporter(ObjectToAssetPath(asset)); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs.meta new file mode 100644 index 0000000..c91088e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/AssetUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9634968648d355c47b7cb12aead7abab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data.meta new file mode 100644 index 0000000..3256935 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8dcc2f4da0b6cea4ab4733ebf32edab4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs new file mode 100644 index 0000000..b2bddd0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs @@ -0,0 +1,19 @@ +namespace AssetStoreTools.Validator.Services.Validation +{ + internal enum ArchiveType + { + None, + TarGz, + Zip, + Rar, + Tar, + TarZip, + Bz2, + LZip, + SevenZip, + GZip, + QuickZip, + Xz, + Wim + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs.meta new file mode 100644 index 0000000..82f4e85 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/ArchiveType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4061cb7aed3883346a66494c23e2e77b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs new file mode 100644 index 0000000..cc89350 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class AssetEnumerator : IEnumerator, IEnumerable where T : Object + { + public const int Capacity = 32; + + private Queue _pathQueue; + private Queue _loadedAssetQueue; + + private T _currentElement; + + public AssetEnumerator(IEnumerable paths) + { + _pathQueue = new Queue(paths); + _loadedAssetQueue = new Queue(); + } + + public bool MoveNext() + { + bool hasPathsButHasNoAssets = _pathQueue.Count != 0 && _loadedAssetQueue.Count == 0; + if (hasPathsButHasNoAssets) + { + LoadMore(); + } + + bool dequeued = false; + if (_loadedAssetQueue.Count != 0) + { + _currentElement = _loadedAssetQueue.Dequeue(); + dequeued = true; + } + + return dequeued; + } + + private void LoadMore() + { + int limit = Capacity; + while (limit > 0 && _pathQueue.Count != 0) + { + string path = _pathQueue.Dequeue(); + T asset = AssetDatabase.LoadAssetAtPath(path); + if (asset != null) + { + _loadedAssetQueue.Enqueue(asset); + limit--; + } + } + + // Unload other loose asset references + EditorUtility.UnloadUnusedAssetsImmediate(); + } + + public void Reset() + { + throw new NotSupportedException("Asset Enumerator cannot be reset."); + } + + public T Current => _currentElement; + + object IEnumerator.Current => Current; + + public void Dispose() + { + // No need to dispose + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this; + } + + public IEnumerator GetEnumerator() + { + return this; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs.meta new file mode 100644 index 0000000..d82fc2f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetEnumerator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0859579889cc56f4aa26eb863a1487b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs new file mode 100644 index 0000000..acd7491 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs @@ -0,0 +1,25 @@ +namespace AssetStoreTools.Validator.Services.Validation +{ + internal enum AssetType + { + All, + Documentation, + Executable, + JPG, + JavaScript, + LossyAudio, + Material, + Mixamo, + Model, + MonoScript, + NonLossyAudio, + PrecompiledAssembly, + Prefab, + Scene, + Shader, + SpeedTree, + Texture, + UnityPackage, + Video + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs.meta new file mode 100644 index 0000000..3acaf4b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/AssetType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b81d00d4ed0a7da4289d4d6248ef9d34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs new file mode 100644 index 0000000..6fe2355 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class LogEntry + { + public string Message; + public LogType Severity; + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs.meta new file mode 100644 index 0000000..cad8e06 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/Data/LogEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1e81104d6b0f4c449ee57503c3b6669 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs new file mode 100644 index 0000000..1831887 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class FileSignatureUtilityService : IFileSignatureUtilityService + { + private class FileSignature + { + public byte[] SignatureBytes; + public int Offset; + + public FileSignature(byte[] signatureBytes, int offset) + { + SignatureBytes = signatureBytes; + Offset = offset; + } + } + + private static readonly Dictionary ArchiveSignatures = new Dictionary + { + { new FileSignature(new byte[] { 0x1f, 0x8b }, 0), ArchiveType.TarGz }, + { new FileSignature(new byte[] { 0x50, 0x4b, 0x03, 0x04 }, 0), ArchiveType.Zip }, + { new FileSignature(new byte[] { 0x50, 0x4b, 0x05, 0x06 }, 0), ArchiveType.Zip }, // Empty Zip Archive + { new FileSignature(new byte[] { 0x50, 0x4b, 0x07, 0x08 }, 0), ArchiveType.Zip }, // Spanned Zip Archive + + { new FileSignature(new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }, 0), ArchiveType.Rar }, // RaR v1.50+ + { new FileSignature(new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 }, 0), ArchiveType.Rar }, // RaR v5.00+ + { new FileSignature(new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 }, 257), ArchiveType.Tar }, + { new FileSignature(new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 }, 257), ArchiveType.Tar }, + { new FileSignature(new byte[] { 0x1f, 0x9d }, 0), ArchiveType.TarZip }, // TarZip LZW algorithm + { new FileSignature(new byte[] { 0x1f, 0xa0 }, 0), ArchiveType.TarZip }, // TarZip LZH algorithm + { new FileSignature(new byte[] { 0x42, 0x5a, 0x68 }, 0), ArchiveType.Bz2 }, + { new FileSignature(new byte[] { 0x4c, 0x5a, 0x49, 0x50 }, 0), ArchiveType.LZip }, + { new FileSignature(new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c }, 0), ArchiveType.SevenZip }, + { new FileSignature(new byte[] { 0x1f, 0x8b }, 0), ArchiveType.GZip }, + { new FileSignature(new byte[] { 0x52, 0x53, 0x56, 0x4b, 0x44, 0x41, 0x54, 0x41 }, 0), ArchiveType.QuickZip }, + { new FileSignature(new byte[] { 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00 }, 0), ArchiveType.Xz }, + { new FileSignature(new byte[] { 0x4D, 0x53, 0x57, 0x49, 0x4D, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00 }, 0), ArchiveType.Wim } + }; + + public ArchiveType GetArchiveType(string filePath) + { + if (!File.Exists(filePath)) + return ArchiveType.None; + + try + { + using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + { + foreach (var kvp in ArchiveSignatures) + { + var fileSignature = kvp.Key; + var archiveType = kvp.Value; + + if (stream.Length < fileSignature.SignatureBytes.Length) + continue; + + var bytes = new byte[fileSignature.SignatureBytes.Length]; + stream.Seek(fileSignature.Offset, SeekOrigin.Begin); + stream.Read(bytes, 0, bytes.Length); + + if (fileSignature.SignatureBytes.SequenceEqual(bytes.Take(fileSignature.SignatureBytes.Length))) + return archiveType; + } + } + } + catch (DirectoryNotFoundException) + { + Debug.LogWarning($"File '{filePath}' exists, but could not be opened for reading. Please make sure the project path lengths are not too long for the Operating System"); + } + + return ArchiveType.None; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs.meta new file mode 100644 index 0000000..fc0dc33 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/FileSignatureUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 695ed79ad88c3b44b8ae41b650ebe16c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs new file mode 100644 index 0000000..9c2d322 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class MeshUtilityService : IMeshUtilityService + { + public IEnumerable GetCustomMeshesInObject(GameObject obj) + { + var meshes = new List(); + + var meshFilters = obj.GetComponentsInChildren(true); + var skinnedMeshes = obj.GetComponentsInChildren(true); + + meshes.AddRange(meshFilters.Select(m => m.sharedMesh)); + meshes.AddRange(skinnedMeshes.Select(m => m.sharedMesh)); + + meshes = meshes.Where(m => AssetDatabase.GetAssetPath(m).StartsWith("Assets/") || + AssetDatabase.GetAssetPath(m).StartsWith("Packages/")).ToList(); + + return meshes; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs.meta new file mode 100644 index 0000000..7c677a2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/MeshUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 307f5dd7be983e246adbda52ac50ecf3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs new file mode 100644 index 0000000..8ef7781 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs @@ -0,0 +1,147 @@ +#if !UNITY_2022_2_OR_NEWER +using System; +using System.Reflection; +#endif +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +#if UNITY_2022_2_OR_NEWER +using UnityEditor.AssetImporters; +#endif +using UnityEngine; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class ModelUtilityService : IModelUtilityService + { + private IAssetUtilityService _assetUtility; + +#if !UNITY_2022_2_OR_NEWER + // Rig fields + private const string RigImportWarningsField = "m_RigImportWarnings"; + private const string RigImportErrorsField = "m_RigImportErrors"; + + // Animation fields + private const string AnimationImportWarningsField = "m_AnimationImportWarnings"; + private const string AnimationImportErrorsField = "m_AnimationImportErrors"; + + private static Editor _modelImporterEditor = null; +#endif + + public ModelUtilityService(IAssetUtilityService assetUtility) + { + _assetUtility = assetUtility; + } + + public Dictionary> GetImportLogs(params Object[] models) + { +#if UNITY_2022_2_OR_NEWER + return GetImportLogsDefault(models); +#else + return GetImportLogsLegacy(models); +#endif + } + +#if UNITY_2022_2_OR_NEWER + private Dictionary> GetImportLogsDefault(params Object[] models) + { + var modelsWithLogs = new Dictionary>(); + + foreach (var model in models) + { + var modelLogs = new List(); + + var importLog = AssetImporter.GetImportLog(_assetUtility.ObjectToAssetPath(model)); + + if (importLog == null) + continue; + + var entries = importLog.logEntries.Where(x => x.flags.HasFlag(ImportLogFlags.Warning) || x.flags.HasFlag(ImportLogFlags.Error)); + foreach (var entry in entries) + { + var severity = entry.flags.HasFlag(ImportLogFlags.Error) ? LogType.Error : LogType.Warning; + modelLogs.Add(new LogEntry() { Message = entry.message, Severity = severity }); + } + + if (modelLogs.Count > 0) + modelsWithLogs.Add(model, modelLogs); + } + + return modelsWithLogs; + } +#endif + +#if !UNITY_2022_2_OR_NEWER + private Dictionary> GetImportLogsLegacy(params Object[] models) + { + var modelsWithLogs = new Dictionary>(); + + foreach (var model in models) + { + var modelLogs = new List(); + + // Load the Model Importer + var modelImporter = _assetUtility.GetAssetImporter(model) as ModelImporter; + + var editorAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name.Equals("UnityEditor")); + + var modelImporterEditorType = editorAssembly.GetType("UnityEditor.ModelImporterEditor"); + + // Load its Model Importer Editor + Editor.CreateCachedEditorWithContext(new Object[] { modelImporter }, model, modelImporterEditorType, ref _modelImporterEditor); + + // Find the base type + var modelImporterEditorTypeBase = _modelImporterEditor.GetType().BaseType; + + // Get the tabs value + var tabsArrayType = modelImporterEditorTypeBase.GetRuntimeProperties().FirstOrDefault(x => x.Name == "tabs"); + var tabsArray = (Array)tabsArrayType.GetValue(_modelImporterEditor); + + // Get the tabs (Model | Rig | Animation | Materials) + var rigTab = tabsArray.GetValue(1); + var animationTab = tabsArray.GetValue(2); + + var rigErrorsCheckSuccess = CheckFieldForSerializedProperty(rigTab, RigImportErrorsField, out var rigErrors); + var rigWarningsCheckSuccess = CheckFieldForSerializedProperty(rigTab, RigImportWarningsField, out var rigWarnings); + var animationErrorsCheckSuccess = CheckFieldForSerializedProperty(animationTab, AnimationImportErrorsField, out var animationErrors); + var animationWarningsCheckSuccess = CheckFieldForSerializedProperty(animationTab, AnimationImportWarningsField, out var animationWarnings); + + if (!rigErrorsCheckSuccess || !rigWarningsCheckSuccess || !animationErrorsCheckSuccess || !animationWarningsCheckSuccess) + UnityEngine.Debug.LogWarning($"An error was encountered when checking import logs for model '{model.name}'"); + + if (!string.IsNullOrEmpty(rigWarnings)) + modelLogs.Add(new LogEntry() { Message = rigWarnings, Severity = LogType.Warning }); + if (!string.IsNullOrEmpty(rigErrors)) + modelLogs.Add(new LogEntry() { Message = rigErrors, Severity = LogType.Error }); + if (!string.IsNullOrEmpty(animationWarnings)) + modelLogs.Add(new LogEntry() { Message = animationWarnings, Severity = LogType.Warning }); + if (!string.IsNullOrEmpty(animationErrors)) + modelLogs.Add(new LogEntry() { Message = animationErrors, Severity = LogType.Error }); + + if (modelLogs.Count > 0) + modelsWithLogs.Add(model, modelLogs); + } + + return modelsWithLogs; + } + + private static bool CheckFieldForSerializedProperty(object source, string propertyName, out string message) + { + message = string.Empty; + + try + { + var propertyType = source.GetType().GetRuntimeFields().FirstOrDefault(x => x.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); + var propertyValue = propertyType.GetValue(source) as SerializedProperty; + message = propertyValue.stringValue; + return true; + } + catch + { + return false; + } + } +#endif + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs.meta new file mode 100644 index 0000000..ec4a95a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ModelUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c50ca4c87e66f1b478279e5d1db4a08e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs new file mode 100644 index 0000000..0402f9e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs @@ -0,0 +1,26 @@ +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class SceneUtilityService : ISceneUtilityService + { + public string CurrentScenePath => SceneManager.GetActiveScene().path; + + public Scene OpenScene(string scenePath) + { + EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo(); + if (string.IsNullOrEmpty(scenePath) || AssetDatabase.LoadAssetAtPath(scenePath) == null) + return EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects); + else + return EditorSceneManager.OpenScene(scenePath); + } + + public GameObject[] GetRootGameObjects() + { + return SceneManager.GetSceneByPath(CurrentScenePath).GetRootGameObjects(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs.meta new file mode 100644 index 0000000..9f20ad4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/SceneUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53e8deb0ebfb7ea47956f3a859580cd4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs new file mode 100644 index 0000000..5812994 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs @@ -0,0 +1,658 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using UnityEditor; +using Object = UnityEngine.Object; + +namespace AssetStoreTools.Validator.Services.Validation +{ + internal class ScriptUtilityService : IScriptUtilityService + { + private const int ScriptTimeoutMs = 10000; + private const string IgnoredAssemblyCharacters = "!@#$%^*&()-+=[]{}\\|;:'\",.<>/?"; + + /// + /// For a given list of script assets, retrieves a list of types and their namespaces + /// + /// + /// A dictionary mapping each script asset with a list of its types. + /// The type tuple contains a name (e.g. class MyClass) and its namespace (e.g. MyNamespace) + /// + public IReadOnlyDictionary> GetTypeNamespacesFromScriptAssets(IList monoScripts) + { + var typesAndNamespaces = new Dictionary>(); + var typeInfos = GetTypeInfosFromScriptAssets(monoScripts); + + foreach (var kvp in typeInfos) + { + var namespacesInScript = new List<(string Name, string Namespace)>(); + foreach (var typeInfo in kvp.Value) + { + bool isValidType = typeInfo.TypeName == ScriptParser.TypeName.Class || typeInfo.TypeName == ScriptParser.TypeName.Struct || + typeInfo.TypeName == ScriptParser.TypeName.Interface || typeInfo.TypeName == ScriptParser.TypeName.Enum; + + if (isValidType) + namespacesInScript.Add(($"{typeInfo.TypeName.ToString().ToLower()} {typeInfo.Name}", typeInfo.Namespace)); + } + + typesAndNamespaces.Add(kvp.Key, namespacesInScript); + } + + return typesAndNamespaces; + } + + /// + /// Scans the given precompiled assembly assets to retrieve a list of their contained types + /// + /// + /// A dictionary mapping each precompiled assembly asset with a list of System.Type objects. + public IReadOnlyDictionary> GetTypesFromAssemblies(IList assemblies) + { + var dllPaths = assemblies.ToDictionary(t => AssetDatabase.GetAssetPath(t), t => t); + var types = new ConcurrentDictionary>(); + var failedDllPaths = new ConcurrentBag(); + + var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + Parallel.ForEach(dllPaths.Keys, + (assemblyPath) => + { + try + { + var assembly = allAssemblies.FirstOrDefault(x => Path.GetFullPath(x.Location).Equals(Path.GetFullPath(assemblyPath), StringComparison.OrdinalIgnoreCase)); + if (assembly == null) + return; + + var assemblyTypes = assembly.GetTypes().Where(x => !IgnoredAssemblyCharacters.Any(c => x.Name.Contains(c))).ToList(); + types.TryAdd(dllPaths[assemblyPath], assemblyTypes); + } + catch + { + failedDllPaths.Add(assemblyPath); + } + }); + + if (failedDllPaths.Count > 0) + { + var message = new StringBuilder("The following precompiled assemblies could not be checked:"); + foreach (var path in failedDllPaths) + message.Append($"\n{path}"); + UnityEngine.Debug.LogWarning(message); + } + + // Types are sorted randomly due to parallelism, therefore need to be sorted before returning + var sortedTypes = dllPaths.Where(x => types.ContainsKey(x.Value)) + .Select(x => new KeyValuePair>(x.Value, types[x.Value])) + .ToDictionary(t => t.Key, t => t.Value); + + return sortedTypes; + } + + /// + /// Scans the given script assets to retrieve a list of their contained types + /// + /// + /// A dictionary mapping each precompiled assembly asset with a list of System.Type objects. + public IReadOnlyDictionary> GetTypesFromScriptAssets(IList monoScripts) + { + var realTypes = new Dictionary>(); + var typeInfos = GetTypeInfosFromScriptAssets(monoScripts); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var kvp in typeInfos) + { + var realTypesInScript = new List(); + foreach (var typeInfo in kvp.Value) + { + bool isValidType = typeInfo.TypeName == ScriptParser.TypeName.Class || typeInfo.TypeName == ScriptParser.TypeName.Struct || + typeInfo.TypeName == ScriptParser.TypeName.Interface || typeInfo.TypeName == ScriptParser.TypeName.Enum; + + if (isValidType) + { + var realType = assemblies.Where(a => a.GetType(typeInfo.GetReflectionFriendlyFullName()) != null) + .Select(a => a.GetType(typeInfo.GetReflectionFriendlyFullName())).FirstOrDefault(); + if (realType != null) + realTypesInScript.Add(realType); + } + } + + realTypes.Add(kvp.Key, realTypesInScript); + } + + return realTypes; + } + + /// + /// Scans the given MonoScript assets to retrieve a list of their contained types + /// + /// + /// A dictionary mapping each script asset with a list of TypeInfo objects. + private IReadOnlyDictionary> GetTypeInfosFromScriptAssets(IList monoScripts) + { + var types = new ConcurrentDictionary>(); + var monoScriptContents = new Dictionary(); + var failedScripts = new ConcurrentBag(); + + // A separate dictionary is needed because MonoScript contents cannot be accessed outside of the main thread + foreach (var kvp in monoScripts) + monoScriptContents.Add(kvp, kvp.text); + + var tasks = new List>(); + + try + { + foreach (var kvp in monoScriptContents) + { + var cancellationTokenSource = new CancellationTokenSource(ScriptTimeoutMs); + + var task = Task.Run(() => + { + var parsingTask = new ScriptParser(cancellationTokenSource.Token); + var parsed = parsingTask.GetTypesInScript(kvp.Value, out IList parsedTypes); + if (parsed) + types.TryAdd(kvp.Key, parsedTypes); + else + failedScripts.Add(kvp.Key); + }); + + tasks.Add(new Tuple(task, cancellationTokenSource)); + } + + foreach (var t in tasks) + t.Item1.Wait(); + } + finally + { + foreach (var t in tasks) + t.Item2.Dispose(); + } + + if (failedScripts.Count > 0) + { + var message = new StringBuilder("The following scripts could not be checked:"); + foreach (var s in failedScripts) + message.Append($"\n{AssetDatabase.GetAssetPath(s)}"); + UnityEngine.Debug.LogWarning(message); + } + + // Types are sorted randomly due to parallelism, therefore need to be sorted before returning + var sortedTypes = monoScriptContents.Where(x => types.ContainsKey(x.Key)) + .Select(x => new KeyValuePair>(x.Key, types[x.Key])) + .ToDictionary(t => t.Key, t => t.Value); + + return sortedTypes; + } + + /// + /// A simple script parser class to detect types declared within a script + /// + private class ScriptParser + { + /// + /// Types that can be identified by the script parser + /// + public enum TypeName + { + Undefined, + Namespace, + Class, + Struct, + Interface, + Enum, + IdentationStart, + IdentationEnd + } + + /// + /// A class containing information about each block of a C# script + /// + /// A block in this context is defined as script text that is contained within curly brackets. + /// If it's a type, it may have a preceding name and a namespace + /// + public class BlockInfo + { + public TypeName TypeName = TypeName.Undefined; + public string Name = string.Empty; + public string FullName = string.Empty; + public string Namespace = string.Empty; + public int FoundIndex; + public int StartIndex; + + public BlockInfo ParentBlock; + + public string GetReflectionFriendlyFullName() + { + StringBuilder sb = new StringBuilder(FullName); + for (int i = sb.Length - 1; i >= Namespace.Length + 1; i--) + if (sb[i] == '.') + sb[i] = '+'; + + return sb.ToString(); + } + } + + private CancellationToken _token; + + public ScriptParser(CancellationToken token) + { + _token = token; + } + + public bool GetTypesInScript(string text, out IList types) + { + types = null; + + try + { + var sanitized = SanitizeScript(text); + types = ScanForTypes(sanitized); + return true; + } + catch + { + return false; + } + } + + private string SanitizeScript(string source) + { + var sb = new StringBuilder(source); + + // Remove comments and strings + sb = RemoveStringsAndComments(sb); + + // Replace newlines with spaces + sb.Replace("\r", " ").Replace("\n", " "); + + // Space out the brackets + sb.Replace("{", " { ").Replace("}", " } "); + + // Insert a space at the start for more convenient parsing + sb.Insert(0, " "); + + // Remove repeating spaces + var sanitized = Regex.Replace(sb.ToString(), @"\s{2,}", " "); + + return sanitized; + } + + private StringBuilder RemoveStringsAndComments(StringBuilder sb) + { + void CheckStringIdentifiers(int index, out bool isVerbatim, out bool isInterpolated) + { + isVerbatim = false; + isInterpolated = false; + + string precedingChars = string.Empty; + for (int i = index - 1; i >= 0; i--) + { + if (sb[i] == ' ') + break; + precedingChars += sb[i]; + } + + if (precedingChars.Contains("@")) + isVerbatim = true; + if (precedingChars.Contains("$")) + isInterpolated = true; + } + + bool IsRegion(int index) + { + if (sb.Length - index < "#region".Length) + return false; + if (sb[index] == '#' && sb[index + 1] == 'r' && sb[index + 2] == 'e' && sb[index + 3] == 'g' && sb[index + 4] == 'i' && + sb[index + 5] == 'o' && sb[index + 6] == 'n') + return true; + return false; + } + + var removeRanges = new List>(); + + for (int i = 0; i < sb.Length; i++) + { + _token.ThrowIfCancellationRequested(); + + // Comment code + if (sb[i] == '/') + { + if (sb[i + 1] == '/') + { + for (int j = i + 1; j < sb.Length; j++) + { + _token.ThrowIfCancellationRequested(); + if (sb[j] == '\n' || j == sb.Length - 1) + { + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j; + break; + } + } + } + else if (sb[i + 1] == '*') + { + for (int j = i + 2; j < sb.Length; j++) + { + _token.ThrowIfCancellationRequested(); + if (sb[j] == '/' && sb[j - 1] == '*') + { + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j + 1; + break; + } + } + } + } + // Char code + else if (sb[i] == '\'') + { + for (int j = i + 1; j < sb.Length; j++) + { + _token.ThrowIfCancellationRequested(); + if (sb[j] == '\'') + { + if (sb[j - 1] == '\\') + { + int slashCount = 0; + int k = j - 1; + while (sb[k--] == '\\') + slashCount++; + if (slashCount % 2 != 0) + continue; + } + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j; + break; + } + } + } + // String code + else if (sb[i] == '"') + { + if (sb[i - 1] == '\'' && sb[i + 1] == '\'' || (sb[i - 2] == '\'' && sb[i - 1] == '\\' && sb[i + 1] == '\'')) + continue; + + CheckStringIdentifiers(i, out bool isVerbatim, out bool isInterpolated); + + var bracketCount = 0; + bool interpolationEnd = true; + for (int j = i + 1; j < sb.Length; j++) + { + _token.ThrowIfCancellationRequested(); + if (isInterpolated && (sb[j] == '{' || sb[j] == '}')) + { + if (sb[j] == '{') + { + if (sb[j + 1] != '{') + bracketCount++; + else + j += 1; + } + else if (sb[j] == '}') + { + if (sb[j + 1] != '}') + bracketCount--; + else + j += 1; + } + + if (bracketCount == 0) + interpolationEnd = true; + else + interpolationEnd = false; + + continue; + } + + if (sb[j] == '\"') + { + if (isVerbatim) + { + if (sb[j + 1] != '\"') + { + if (!isInterpolated || isInterpolated && interpolationEnd == true) + { + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j + 1; + break; + } + } + else + j += 1; + } + else + { + bool endOfComment = false; + if (sb[j - 1] != '\\') + endOfComment = true; + else + { + int slashCount = 0; + int k = j - 1; + while (sb[k--] == '\\') + slashCount++; + if (slashCount % 2 == 0) + endOfComment = true; + } + + if (!isInterpolated && endOfComment || (isInterpolated && interpolationEnd && endOfComment)) + { + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j + 1; + break; + } + } + } + } + } + // Region code + else if (IsRegion(i)) + { + i += "#region".Length; + for (int j = i; j < sb.Length; j++) + { + _token.ThrowIfCancellationRequested(); + if (sb[j] == '\n') + { + removeRanges.Add(new Tuple(i, j - i + 1)); + i = j; + break; + } + } + } + } + + for (int i = removeRanges.Count - 1; i >= 0; i--) + sb = sb.Remove(removeRanges[i].Item1, removeRanges[i].Item2); + + return sb; + } + + private IList ScanForTypes(string script) + { + var typeList = new SortedList(); + BlockInfo currentActiveBlock = new BlockInfo(); + + int i = 0; + + BlockInfo nextNamespace = null; + BlockInfo nextClass = null; + BlockInfo nextStruct = null; + BlockInfo nextInterface = null; + BlockInfo nextEnum = null; + + while (i < script.Length) + { + _token.ThrowIfCancellationRequested(); + if (nextNamespace == null) + nextNamespace = FindNextTypeBlock(script, i, TypeName.Namespace); + if (nextClass == null) + nextClass = FindNextTypeBlock(script, i, TypeName.Class); + if (nextStruct == null) + nextStruct = FindNextTypeBlock(script, i, TypeName.Struct); + if (nextInterface == null) + nextInterface = FindNextTypeBlock(script, i, TypeName.Interface); + if (nextEnum == null) + nextEnum = FindNextTypeBlock(script, i, TypeName.Enum); + + var nextIdentationIncrease = FindNextTypeBlock(script, i, TypeName.IdentationStart); + var nextIdentationDecrease = FindNextTypeBlock(script, i, TypeName.IdentationEnd); + + if (!TryFindClosestBlock(out var closestBlock, nextNamespace, nextClass, + nextStruct, nextInterface, nextEnum, nextIdentationIncrease, nextIdentationDecrease)) + break; + + switch (closestBlock) + { + case var _ when closestBlock == nextIdentationIncrease: + closestBlock.ParentBlock = currentActiveBlock; + currentActiveBlock = closestBlock; + break; + case var _ when closestBlock == nextIdentationDecrease: + if (currentActiveBlock.TypeName != TypeName.Undefined) + typeList.Add(currentActiveBlock.StartIndex, currentActiveBlock); + currentActiveBlock = currentActiveBlock.ParentBlock; + break; + case var _ when closestBlock == nextNamespace: + closestBlock.Namespace = currentActiveBlock.TypeName == TypeName.Namespace ? currentActiveBlock.FullName : currentActiveBlock.Namespace; + closestBlock.FullName = string.IsNullOrEmpty(currentActiveBlock.FullName) ? closestBlock.Name : $"{currentActiveBlock.FullName}.{closestBlock.Name}"; + closestBlock.ParentBlock = currentActiveBlock; + currentActiveBlock = closestBlock; + nextNamespace = null; + break; + case var _ when closestBlock == nextClass: + case var _ when closestBlock == nextStruct: + case var _ when closestBlock == nextInterface: + case var _ when closestBlock == nextEnum: + closestBlock.FullName = string.IsNullOrEmpty(currentActiveBlock.FullName) ? closestBlock.Name : $"{currentActiveBlock.FullName}.{closestBlock.Name}"; + closestBlock.Namespace = currentActiveBlock.TypeName == TypeName.Namespace ? currentActiveBlock.FullName : currentActiveBlock.Namespace; + closestBlock.ParentBlock = currentActiveBlock; + currentActiveBlock = closestBlock; + switch (closestBlock) + { + case var _ when closestBlock == nextClass: + nextClass = null; + break; + case var _ when closestBlock == nextStruct: + nextStruct = null; + break; + case var _ when closestBlock == nextInterface: + nextInterface = null; + break; + case var _ when closestBlock == nextEnum: + nextEnum = null; + break; + } + break; + } + + i = closestBlock.StartIndex; + } + + return typeList.Select(x => x.Value).ToList(); + } + + private bool TryFindClosestBlock(out BlockInfo closestBlock, params BlockInfo[] blocks) + { + closestBlock = null; + for (int i = 0; i < blocks.Length; i++) + { + if (blocks[i].FoundIndex == -1) + continue; + + if (closestBlock == null || closestBlock.FoundIndex > blocks[i].FoundIndex) + closestBlock = blocks[i]; + } + + return closestBlock != null; + } + + private BlockInfo FindNextTypeBlock(string text, int startIndex, TypeName blockType) + { + string typeKeyword; + switch (blockType) + { + case TypeName.Namespace: + typeKeyword = "namespace"; + break; + case TypeName.Class: + typeKeyword = "class"; + break; + case TypeName.Struct: + typeKeyword = "struct"; + break; + case TypeName.Interface: + typeKeyword = "interface"; + break; + case TypeName.Enum: + typeKeyword = "enum"; + break; + case TypeName.IdentationStart: + var identationStart = text.IndexOf("{", startIndex); + return new BlockInfo() { FoundIndex = identationStart, StartIndex = identationStart + 1, TypeName = TypeName.Undefined }; + case TypeName.IdentationEnd: + var identationEnd = text.IndexOf("}", startIndex); + return new BlockInfo() { FoundIndex = identationEnd, StartIndex = identationEnd + 1, TypeName = TypeName.Undefined }; + default: + throw new ArgumentException("Invalid block type provided"); + } + + int start = -1; + int blockStart = -1; + string name = string.Empty; + while (startIndex < text.Length) + { + _token.ThrowIfCancellationRequested(); + start = text.IndexOf($" {typeKeyword} ", startIndex); + if (start == -1) + return new BlockInfo { FoundIndex = -1 }; + + // Check if the caught type keyword matches the type definition + var openingBracket = text.IndexOf("{", start); + if (openingBracket == -1) + return new BlockInfo { FoundIndex = -1 }; + + var declaration = text.Substring(start, openingBracket - start); + var split = declaration.Split(' '); + + // Namespace detection + if (typeKeyword == "namespace") + { + // Expected result: [null] [namespace] [null] + if (split.Length == 4) + { + name = split[2]; + blockStart = openingBracket + 1; + break; + } + else + startIndex = openingBracket + 1; + } + // Class, Interface, Struct, Enum detection + else + { + // Expected result: [null] [keywordName] [typeName] ... [null] + // Skip any keywords that only contains [null] [keywordName] [null] + if (split.Length != 3) + { + name = split[2]; + blockStart = openingBracket + 1; + break; + } + else + startIndex = openingBracket + 1; + } + } + + var info = new BlockInfo() { FoundIndex = start, StartIndex = blockStart, Name = name, TypeName = blockType }; + return info; + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs.meta new file mode 100644 index 0000000..76d8018 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/Validation/ScriptUtilityService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9db4298044e2add44bc3aa6ba898d7c3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs new file mode 100644 index 0000000..67f62ef --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs @@ -0,0 +1,24 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.Services.Validation; + +namespace AssetStoreTools.Validator.Services +{ + internal class ValidatorServiceProvider : ServiceProvider + { + public static ValidatorServiceProvider Instance => _instance ?? (_instance = new ValidatorServiceProvider()); + private static ValidatorServiceProvider _instance; + + private ValidatorServiceProvider() { } + + protected override void RegisterServices() + { + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + Register(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs.meta new file mode 100644 index 0000000..d2f5d3d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Services/ValidatorServiceProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47ac495c61171824abb2b72b1b7ef676 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions.meta new file mode 100644 index 0000000..7b3ebc2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 462cf5f916fad974a831f6a44aadcc82 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs new file mode 100644 index 0000000..3d92470 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs @@ -0,0 +1,121 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal class AutomatedTest : ValidationTest + { + public AutomatedTest(ValidationTestScriptableObject source) : base(source) { } + + public override void Run(ITestConfig config) + { + Type testClass = null; + MethodInfo testMethod = null; + + try + { + ValidateTestMethod(ref testClass, ref testMethod); + ValidateConfig(config); + } + catch (Exception e) + { + Debug.LogError(e.Message); + return; + } + + object testClassInstance; + try + { + testClassInstance = CreateInstance(testClass, config); + } + catch (Exception e) + { + Debug.LogError($"Could not create an instance of class {testClass}:\n{e}"); + return; + } + + try + { + Result = (TestResult)testMethod.Invoke(testClassInstance, new object[0]); + } + catch (Exception e) + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + result.AddMessage("An exception was caught when running this test case. See Console for more details"); + Debug.LogError($"An exception was caught when running validation for test case '{Title}'\n{e}"); + Result = result; + } + } + + private void ValidateTestMethod(ref Type testClass, ref MethodInfo testMethod) + { + if (TestScript == null || (testClass = TestScript.GetClass()) == null) + throw new Exception($"Cannot run test {Title} - Test Script class was not found"); + + var interfaces = testClass.GetInterfaces(); + if (!interfaces.Contains(typeof(ITestScript))) + throw new Exception($"Cannot run test {Title} - Test Script class is not derived from {nameof(ITestScript)}"); + + testMethod = testClass.GetMethod("Run"); + if (testMethod == null) + throw new Exception($"Cannot run test {Title} - Run() method was not found"); + } + + private void ValidateConfig(ITestConfig config) + { + switch (ValidationType) + { + case ValidationType.Generic: + case ValidationType.UnityPackage: + if (config is GenericTestConfig) + return; + break; + default: + throw new NotImplementedException("Undefined validation type"); + } + + throw new Exception("Config does not match the validation type"); + } + + private object CreateInstance(Type testClass, ITestConfig testConfig) + { + var constructors = testClass.GetConstructors(); + if (constructors.Length != 1) + throw new Exception($"Test class {testClass} should only contain a single constructor"); + + var constructor = constructors[0]; + var expectedParameters = constructor.GetParameters(); + var parametersToUse = new List(); + foreach (var expectedParam in expectedParameters) + { + var paramType = expectedParam.ParameterType; + + if (paramType == testConfig.GetType()) + { + parametersToUse.Add(testConfig); + continue; + } + + if (typeof(IValidatorService).IsAssignableFrom(paramType)) + { + var matchingService = ValidatorServiceProvider.Instance.GetService(paramType); + if (matchingService == null) + throw new Exception($"Service {paramType} is not registered and could not be retrieved"); + + parametersToUse.Add(matchingService); + continue; + } + + throw new Exception($"Invalid parameter type: {paramType}"); + } + + var instance = constructor.Invoke(parametersToUse.ToArray()); + return instance; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs.meta new file mode 100644 index 0000000..14f79c3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/AutomatedTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b284048af6fef0d49b8c3a37f7083d04 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs new file mode 100644 index 0000000..e91c0ae --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs @@ -0,0 +1,7 @@ +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal class GenericTestConfig : ITestConfig + { + public string[] ValidationPaths { get; set; } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs.meta new file mode 100644 index 0000000..d982131 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/GenericTestConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ba1ae4e7b45a6c84ca8ad0eb391bf95d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs new file mode 100644 index 0000000..4c4d858 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs @@ -0,0 +1,4 @@ +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal interface ITestConfig { } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs.meta new file mode 100644 index 0000000..ee42939 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7e57766d04022c4dac58caf8ebe339a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs new file mode 100644 index 0000000..aa645c0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs @@ -0,0 +1,9 @@ +using AssetStoreTools.Validator.Data; + +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal interface ITestScript + { + TestResult Run(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs.meta new file mode 100644 index 0000000..f8c4743 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ITestScript.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 839ef1f3e773ab347b66932d3f810aec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects.meta new file mode 100644 index 0000000..1dfe2af --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d62652f91f698904ea662c6ab252ea59 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs new file mode 100644 index 0000000..fa493fb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs @@ -0,0 +1,11 @@ +#if UNITY_ASTOOLS_DEVELOPMENT +using UnityEngine; +#endif + +namespace AssetStoreTools.Validator.TestDefinitions +{ +#if UNITY_ASTOOLS_DEVELOPMENT + [CreateAssetMenu(fileName = "AutomatedTest", menuName = "Asset Store Validator/Automated Test")] +#endif + internal class AutomatedTestScriptableObject : ValidationTestScriptableObject { } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs.meta new file mode 100644 index 0000000..28f0608 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/AutomatedTestScriptableObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d813ff809ae82f643bf975031305d541 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor.meta new file mode 100644 index 0000000..eebb867 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7cd52466a2239344d90c3043b7afc1e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs new file mode 100644 index 0000000..36025a8 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs @@ -0,0 +1,196 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Utility; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestDefinitions +{ + [CustomEditor(typeof(ValidationTestScriptableObject), true)] + internal class ValidationTestScriptableObjectInspector : UnityEditor.Editor + { + private enum FilterSeverity + { + Warning, + Fail + } + + private enum FilterType + { + UseFilter, + ExcludeFilter + } + + private ValidationTestScriptableObject _data; + private ValidationTestScriptableObject[] _allObjects; + + private SerializedProperty _script; + private SerializedProperty _validationType; + + private SerializedProperty _testScript; + private SerializedProperty _category; + private SerializedProperty _failFilterProperty; + private SerializedProperty _isInclusiveProperty; + private SerializedProperty _appliesToSubCategories; + private SerializedProperty _categoryFilter; + + private bool _hadChanges; + + private void OnEnable() + { + if (target == null) return; + + _data = target as ValidationTestScriptableObject; + + _script = serializedObject.FindProperty("m_Script"); + + _validationType = serializedObject.FindProperty(nameof(ValidationTestScriptableObject.ValidationType)); + + _testScript = serializedObject.FindProperty(nameof(ValidationTestScriptableObject.TestScript)); + _category = serializedObject.FindProperty(nameof(ValidationTestScriptableObject.CategoryInfo)); + _failFilterProperty = _category.FindPropertyRelative(nameof(ValidationTestScriptableObject.CategoryInfo.IsFailFilter)); + _isInclusiveProperty = _category.FindPropertyRelative(nameof(ValidationTestScriptableObject.CategoryInfo.IsInclusiveFilter)); + _appliesToSubCategories = _category.FindPropertyRelative(nameof(ValidationTestScriptableObject.CategoryInfo.AppliesToSubCategories)); + _categoryFilter = _category.FindPropertyRelative(nameof(ValidationTestScriptableObject.CategoryInfo.Filter)); + + _allObjects = ValidatorUtility.GetAutomatedTestCases(ValidatorUtility.SortType.Id); + _hadChanges = false; + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.LabelField(GetInspectorTitle(), new GUIStyle(EditorStyles.centeredGreyMiniLabel) { fontSize = 24 }, GUILayout.MinHeight(50)); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.PropertyField(_script); + + EditorGUI.BeginChangeCheck(); + // ID field + EditorGUILayout.IntField("Test Id", _data.Id); + if (!ValidateID()) + EditorGUILayout.HelpBox("ID is already in use", MessageType.Warning); + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.Space(8); + EditorGUILayout.LabelField("Test Data", new GUIStyle(EditorStyles.centeredGreyMiniLabel) { alignment = TextAnchor.MiddleLeft, fontSize = 14, padding = new RectOffset(0, 0, 0, 0) }); + + // Validation Type + var validationType = (ValidationType)EditorGUILayout.EnumPopup("Validation Type", (ValidationType)_validationType.enumValueIndex); + _validationType.enumValueIndex = (int)validationType; + + // Other fields + _data.Title = EditorGUILayout.TextField("Title", _data.Title); + if (string.IsNullOrEmpty(_data.Title)) + EditorGUILayout.HelpBox("Title cannot be empty", MessageType.Warning); + + EditorGUILayout.LabelField("Description"); + GUIStyle myTextAreaStyle = new GUIStyle(EditorStyles.textArea) { wordWrap = true }; + _data.Description = EditorGUILayout.TextArea(_data.Description, myTextAreaStyle); + + // Test script + EditorGUILayout.Space(8); + EditorGUILayout.LabelField("Test Script", new GUIStyle(EditorStyles.centeredGreyMiniLabel) { alignment = TextAnchor.MiddleLeft, fontSize = 14, padding = new RectOffset(0, 0, 0, 0) }); + + EditorGUILayout.PropertyField(_testScript); + if (_testScript.objectReferenceValue != null) + { + var generatedScriptType = (_testScript.objectReferenceValue as MonoScript).GetClass(); + if (generatedScriptType == null || !generatedScriptType.GetInterfaces().Contains(typeof(ITestScript))) + EditorGUILayout.HelpBox($"Test Script does not derive from {nameof(ITestScript)}. Test execution will fail", MessageType.Warning); + } + else if (!string.IsNullOrEmpty(_data.Title)) + { + var generatedScriptName = GenerateTestScriptName(); + EditorGUILayout.LabelField($"Proposed script name: {generatedScriptName}.cs", new GUIStyle("Label") { richText = true }); + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Generate Test Method Script", GUILayout.MaxWidth(200f))) + { + var generatedScript = ValidatorUtility.GenerateTestScript(generatedScriptName, (ValidationType)_validationType.enumValueIndex); + _testScript.objectReferenceValue = generatedScript; + } + EditorGUILayout.EndHorizontal(); + } + + // Variable Sevetity Options + EditorGUILayout.Space(8); + EditorGUILayout.LabelField("Variable Severity Status Filtering", new GUIStyle(EditorStyles.centeredGreyMiniLabel) { alignment = TextAnchor.MiddleLeft, fontSize = 14, padding = new RectOffset(0, 0, 0, 0) }); + + var filterSeverity = (FilterSeverity)EditorGUILayout.EnumPopup("Fail Type", _failFilterProperty.boolValue ? FilterSeverity.Fail : FilterSeverity.Warning); + _failFilterProperty.boolValue = filterSeverity == FilterSeverity.Fail ? true : false; + var filterType = (FilterType)EditorGUILayout.EnumPopup("Filtering rule", _isInclusiveProperty.boolValue ? FilterType.UseFilter : FilterType.ExcludeFilter); + _isInclusiveProperty.boolValue = filterType == FilterType.UseFilter ? true : false; + + EditorGUILayout.PropertyField(_appliesToSubCategories); + + EditorGUILayout.Space(10); + + EditorGUILayout.BeginHorizontal(GUI.skin.FindStyle("HelpBox")); + EditorGUILayout.LabelField(GetFilterDescription(_failFilterProperty.boolValue, _isInclusiveProperty.boolValue), new GUIStyle(GUI.skin.label) { wordWrap = true, richText = true }); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(10); + + EditorGUILayout.PropertyField(_categoryFilter); + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(target); + _hadChanges = true; + } + + _hadChanges = serializedObject.ApplyModifiedProperties() || _hadChanges; + } + + private string GetInspectorTitle() + { + switch (_data) + { + case AutomatedTestScriptableObject _: + return "Automated Test"; + default: + return "Miscellaneous Test"; + } + } + + private string GenerateTestScriptName() + { + var name = _data.Title.Replace(" ", ""); + return name; + } + + private string GetFilterDescription(bool isFailFilter, bool isInclusive) + { + string text = $"When a {TestResultStatus.VariableSeverityIssue} result type is returned from the test method:\n\n"; + if (isFailFilter) + { + if (isInclusive) + return text + "• Categories IN the filter will result in a FAIL.\n• Categories NOT in the filter will result in a WARNING"; + else + return text + "• Categories NOT in the filter will result in a FAIL.\n• Categories IN the filter will result in a WARNING"; + } + else + { + if (isInclusive) + return text + "• Categories IN the filter will result in a WARNING.\n• Categories NOT in the filter will result in a FAIL"; + else + return text + "• Categories NOT in the filter will result in a WARNING.\n• Categories IN the filter will result in a FAIL"; + } + } + + private bool ValidateID() + { + return !_allObjects.Any(x => x.Id == _data.Id && x != _data); + } + + private void OnDisable() + { + if (!_hadChanges) return; + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs.meta new file mode 100644 index 0000000..37b320c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/Editor/ValidationTestScriptableObjectInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06d76b0e6df91eb43ac956f883c4a2da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs new file mode 100644 index 0000000..d700dfb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs @@ -0,0 +1,35 @@ +using AssetStoreTools.Validator.Categories; +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Utility; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal abstract class ValidationTestScriptableObject : ScriptableObject + { + [SerializeField, HideInInspector] + private bool HasBeenInitialized; + + public int Id; + public string Title; + public string Description; + public ValidatorCategory CategoryInfo; + public ValidationType ValidationType; + public MonoScript TestScript; + + private void OnEnable() + { + // To do: maybe replace with Custom Inspector + if (HasBeenInitialized) + return; + + var existingTestCases = ValidatorUtility.GetAutomatedTestCases(ValidatorUtility.SortType.Id); + if (existingTestCases.Length > 0) + Id = existingTestCases[existingTestCases.Length - 1].Id + 1; + else + Id = 1; + HasBeenInitialized = true; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs.meta new file mode 100644 index 0000000..66bee1d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/Scriptable Objects/ValidationTestScriptableObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11c2422f057b75a458e184d169a00eb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs new file mode 100644 index 0000000..4a4bb66 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Categories; +using AssetStoreTools.Validator.Data; +using UnityEditor; + +namespace AssetStoreTools.Validator.TestDefinitions +{ + internal abstract class ValidationTest + { + public int Id; + public string Title; + public string Description; + public MonoScript TestScript; + + public ValidationType ValidationType; + public ValidatorCategory CategoryInfo; + + public TestResult Result; + + protected ValidationTest(ValidationTestScriptableObject source) + { + Id = source.Id; + Title = source.Title; + Description = source.Description; + TestScript = source.TestScript; + CategoryInfo = source.CategoryInfo; + ValidationType = source.ValidationType; + Result = new TestResult(); + } + + public abstract void Run(ITestConfig config); + + public string Slugify(string value) + { + string newValue = value.Replace(' ', '-').ToLower(); + return newValue; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs.meta new file mode 100644 index 0000000..5f7071b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Definitions/ValidationTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 095d629656748914bb6202598876fdf4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods.meta new file mode 100644 index 0000000..5a3363e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: daedaf78228b5184297e7ca334ea2a12 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic.meta new file mode 100644 index 0000000..a9b691e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ecfb23f95f16d2347a4063411aad8063 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs new file mode 100644 index 0000000..c54c43a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs @@ -0,0 +1,64 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckAnimationClips : ITestScript + { + private static readonly string[] InvalidNames = new[] { "Take 001" }; + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckAnimationClips(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + var badModels = new Dictionary>(); + var models = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Model); + + foreach (var model in models) + { + var badClips = new List(); + var clips = AssetDatabase.LoadAllAssetsAtPath(_assetUtility.ObjectToAssetPath(model)); + foreach (var clip in clips) + { + if (InvalidNames.Any(x => x.ToLower().Equals(clip.name.ToLower()))) + { + badClips.Add(clip); + } + } + + if (badClips.Count > 0) + badModels.Add(model, badClips); + } + + if (badModels.Count > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following models have animation clips with invalid names. Animation clip names should be unique and reflective of the animation itself"); + foreach (var kvp in badModels) + { + result.AddMessage(_assetUtility.ObjectToAssetPath(kvp.Key), null, kvp.Value.ToArray()); + } + } + else + { + result.AddMessage("No animation clips with invalid names were found!"); + result.Status = TestResultStatus.Pass; + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs.meta new file mode 100644 index 0000000..4529bdf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAnimationClips.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a28985886f182c4bacc89a44777c742 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs new file mode 100644 index 0000000..be8e384 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs @@ -0,0 +1,128 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckAudioClipping : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckAudioClipping(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + // How many peaks above threshold are required for Audio Clips to be considered clipping + const int TOLERANCE = 2; + // Min. amount of consecutive samples above threshold required for peak detection + const int PEAK_STEPS = 1; + // Clipping threshold. More lenient here than Submission Guidelines (-0.3db) due to the problematic nature of + // correctly determining how sensitive the audio clipping flagging should be, as well as to account for any + // distortion introduced when AudioClips are compresssed after importing to Unity. + const float THRESHOLD = -0.05f; + // Samples for 16-bit audio files + const float S16b = 32767f; + float clippingThreshold = (S16b - (S16b / (2 * Mathf.Log10(1 / S16b)) * THRESHOLD)) / S16b; + TestResult result = new TestResult(); + var clippingAudioClips = new Dictionary(); + + var losslessAudioClips = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.NonLossyAudio).Select(x => x as AudioClip).ToList(); + foreach (var clip in losslessAudioClips) + { + var path = AssetDatabase.GetAssetPath(clip.GetInstanceID()); + + if (IsClipping(clip, TOLERANCE, PEAK_STEPS, clippingThreshold)) + clippingAudioClips.Add(clip, path); + } + + var lossyAudioClips = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.LossyAudio).Select(x => x as AudioClip).ToList(); + foreach (var clip in lossyAudioClips) + { + var path = AssetDatabase.GetAssetPath(clip.GetInstanceID()); + + if (IsClipping(clip, TOLERANCE, PEAK_STEPS, clippingThreshold)) + clippingAudioClips.Add(clip, path); + } + + if (clippingAudioClips.Count > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following AudioClips are clipping or are very close to 0db ceiling. Please ensure your exported audio files have at least 0.3db of headroom (should peak at no more than -0.3db):", null, clippingAudioClips.Select(x => x.Key).ToArray()); + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No clipping audio files were detected."); + } + + return result; + } + + private bool IsClipping(AudioClip clip, int tolerance, int peakTolerance, float clippingThreshold) + { + if (DetectNumPeaksAboveThreshold(clip, peakTolerance, clippingThreshold) >= tolerance) + return true; + + return false; + } + + private int DetectNumPeaksAboveThreshold(AudioClip clip, int peakTolerance, float clippingThreshold) + { + float[] samples = new float[clip.samples * clip.channels]; + var data = clip.GetData(samples, 0); + + float[] samplesLeft = samples.Where((s, i) => i % 2 == 0).ToArray(); + float[] samplesRight = samples.Where((s, i) => i % 2 == 1).ToArray(); + + int peaks = 0; + + peaks = GetPeaksInChannel(samplesLeft, peakTolerance, clippingThreshold) + + GetPeaksInChannel(samplesRight, peakTolerance, clippingThreshold); + + return peaks; + } + + private int GetPeaksInChannel(float[] samples, int peakTolerance, float clippingThreshold) + { + int peaks = 0; + bool evalPeak = false; + int peakSteps = 0; + int step = 0; + + while (step < samples.Length) + { + if (Mathf.Abs(samples[step]) >= clippingThreshold && evalPeak) + { + peakSteps++; + } + + if (Mathf.Abs(samples[step]) >= clippingThreshold && !evalPeak) + { + evalPeak = true; + peakSteps++; + } + + if (Mathf.Abs(samples[step]) < clippingThreshold && evalPeak) + { + evalPeak = false; + if (peakSteps >= peakTolerance) + peaks++; + peakSteps = 0; + } + + step++; + } + + return peaks; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs.meta new file mode 100644 index 0000000..06f0892 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckAudioClipping.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f604db0353da0cb46bb048f5cd37186f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs new file mode 100644 index 0000000..c8be5bb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs @@ -0,0 +1,55 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckColliders : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IMeshUtilityService _meshUtility; + + public CheckColliders(GenericTestConfig config, IAssetUtilityService assetUtility, IMeshUtilityService meshUtility) + { + _config = config; + _assetUtility = assetUtility; + _meshUtility = meshUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var prefabs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Prefab); + var badPrefabs = new List(); + + foreach (var p in prefabs) + { + var meshes = _meshUtility.GetCustomMeshesInObject(p); + + if (!p.isStatic || !meshes.Any()) + continue; + + var colliders = p.GetComponentsInChildren(true); + if (!colliders.Any()) + badPrefabs.Add(p); + } + + if (badPrefabs.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found prefabs have colliders!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following prefabs contain meshes, but colliders were not found", null, badPrefabs.ToArray()); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs.meta new file mode 100644 index 0000000..c90549a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckColliders.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 308b3d7b7a883b949a14f47cfd5c7ebe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs new file mode 100644 index 0000000..6f8ce60 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs @@ -0,0 +1,121 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckCompressedFiles : ITestScript + { + private enum ArchiveResult + { + Allowed, + NotAllowed, + TarGzWithIssues, + ZipWithIssues + } + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IFileSignatureUtilityService _fileSignatureUtility; + + public CheckCompressedFiles(GenericTestConfig config, IAssetUtilityService assetUtility, IFileSignatureUtilityService fileSignatureUtility) + { + _config = config; + _assetUtility = assetUtility; + _fileSignatureUtility = fileSignatureUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var checkedArchives = new Dictionary(); + + // Retrieving assets via GetObjectsFromAssets() is insufficient because + // archives might be renamed and not use the expected extension + var allAssetPaths = _assetUtility.GetAssetPathsFromAssets(_config.ValidationPaths, AssetType.All); + + foreach (var assetPath in allAssetPaths) + { + ArchiveType archiveType; + if ((archiveType = _fileSignatureUtility.GetArchiveType(assetPath)) == ArchiveType.None) + continue; + + var archiveObj = _assetUtility.AssetPathToObject(assetPath); + + switch (archiveType) + { + case ArchiveType.TarGz: + if (assetPath.ToLower().EndsWith(".unitypackage")) + checkedArchives.Add(archiveObj, ArchiveResult.Allowed); + else + checkedArchives.Add(archiveObj, ArchiveResult.TarGzWithIssues); + break; + case ArchiveType.Zip: + if (FileNameContainsKeyword(assetPath, "source") && assetPath.ToLower().EndsWith(".zip")) + checkedArchives.Add(archiveObj, ArchiveResult.Allowed); + else + checkedArchives.Add(archiveObj, ArchiveResult.ZipWithIssues); + break; + default: + checkedArchives.Add(archiveObj, ArchiveResult.NotAllowed); + break; + } + } + + if (checkedArchives.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No archives were found in the package content!"); + return result; + } + + if (checkedArchives.Any(x => x.Value == ArchiveResult.Allowed)) + { + result.Status = TestResultStatus.Warning; + result.AddMessage("The following archives of allowed format were found in the package content.\n" + + "Please make sure they adhere to the nested archive guidelines:", null, + checkedArchives.Where(x => x.Value == ArchiveResult.Allowed).Select(x => x.Key).ToArray()); + } + + if (checkedArchives.Any(x => x.Value == ArchiveResult.TarGzWithIssues)) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following .gz archives were found in the package content.\n" + + "• Gz archives are only allowed in form of '.unitypackage' files", null, + checkedArchives.Where(x => x.Value == ArchiveResult.TarGzWithIssues).Select(x => x.Key).ToArray()); + } + + if (checkedArchives.Any(x => x.Value == ArchiveResult.ZipWithIssues)) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following .zip archives were found in the package content.\n" + + "• Zip archives should contain the keyword 'source' in the file name", null, + checkedArchives.Where(x => x.Value == ArchiveResult.ZipWithIssues).Select(x => x.Key).ToArray()); + } + + if (checkedArchives.Any(x => x.Value == ArchiveResult.NotAllowed)) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following archives are using formats that are not allowed:", null, + checkedArchives.Where(x => x.Value == ArchiveResult.NotAllowed).Select(x => x.Key).ToArray()); + } + + return result; + } + + private bool FileNameContainsKeyword(string filePath, string keyword) + { + var fileInfo = new FileInfo(filePath); + + if (!fileInfo.Exists) + return false; + + return fileInfo.Name.Remove(fileInfo.Name.Length - fileInfo.Extension.Length).ToLower().Contains(keyword.ToLower()); + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs.meta new file mode 100644 index 0000000..8bd989c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckCompressedFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84b23febe0d923842aef73b95da5f25b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs new file mode 100644 index 0000000..e12234c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs @@ -0,0 +1,46 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckEmptyPrefabs : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckEmptyPrefabs(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var prefabs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Prefab); + var badPrefabs = new List(); + + foreach (var p in prefabs) + { + if (p.GetComponents().Length == 1 && p.transform.childCount == 0) + badPrefabs.Add(p); + } + + if (badPrefabs.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No empty prefabs were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following prefabs are empty", null, badPrefabs.ToArray()); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs.meta new file mode 100644 index 0000000..fafd181 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckEmptyPrefabs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8055bed9373283e4793463b90b42f08f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs new file mode 100644 index 0000000..940cc38 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs @@ -0,0 +1,153 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckFileMenuNames : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IScriptUtilityService _scriptUtility; + + public CheckFileMenuNames(GenericTestConfig config, IAssetUtilityService assetUtility, IScriptUtilityService scriptUtility) + { + _config = config; + _assetUtility = assetUtility; + _scriptUtility = scriptUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + + #region Scripts + + var scripts = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.MonoScript).ToArray(); + var scriptTypes = _scriptUtility.GetTypesFromScriptAssets(scripts); + var affectedScripts = new Dictionary>(); + + foreach (var kvp in scriptTypes) + { + var badMethods = new List(); + foreach (var type in kvp.Value) + { + foreach (var method in type.GetMethods(bindingFlags)) + { + var attributes = method.GetCustomAttributes().ToList(); + if (attributes.Count == 0) + continue; + + var badAttributes = attributes.Where(x => !IsValidMenuItem(x.menuItem)).ToList(); + if (badAttributes.Count > 0) + badMethods.Add($"{string.Join("\n", badAttributes.Select(x => $"\'{x.menuItem}\'"))}\n(for method '{method.Name}')\n"); + } + } + + if (badMethods.Count > 0) + affectedScripts.Add(kvp.Key, badMethods); + } + + #endregion + + #region Precompiled Assemblies + + var assemblies = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.PrecompiledAssembly).ToArray(); + var assemblyTypes = _scriptUtility.GetTypesFromAssemblies(assemblies); + var affectedAssemblies = new Dictionary>(); + + foreach (var kvp in assemblyTypes) + { + var badMethods = new List(); + foreach (var type in kvp.Value) + { + foreach (var method in type.GetMethods(bindingFlags)) + { + var attributes = method.GetCustomAttributes().ToList(); + if (attributes.Count == 0) + continue; + + var badAttributes = attributes.Where(x => !IsValidMenuItem(x.menuItem)).ToList(); + if (badAttributes.Count > 0) + badMethods.Add($"{string.Join("\n", badAttributes.Select(x => (x as MenuItem).menuItem))}\n(Method '{method.Name}')\n"); + } + } + + if (badMethods.Count > 0) + affectedAssemblies.Add(kvp.Key, badMethods); + } + + #endregion + + if (affectedScripts.Count > 0 || affectedAssemblies.Count > 0) + { + if (affectedScripts.Count > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following scripts contain invalid MenuItem names:"); + foreach (var kvp in affectedScripts) + { + var message = string.Empty; + foreach (var type in kvp.Value) + message += type + "\n"; + + message = message.Remove(message.Length - "\n".Length); + result.AddMessage(message, null, kvp.Key); + } + } + + if (affectedAssemblies.Count > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following assemblies contain invalid MenuItem names:"); + foreach (var kvp in affectedAssemblies) + { + var message = string.Empty; + foreach (var type in kvp.Value) + message += type + "\n"; + + message = message.Remove(message.Length - "\n".Length); + result.AddMessage(message, null, kvp.Key); + } + } + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No MenuItems with invalid names were found!"); + } + + return result; + } + + private bool IsValidMenuItem(string menuItemName) + { + var acceptableMenuItems = new string[] + { + "File", + "Edit", + "Assets", + "GameObject", + "Component", + "Window", + "Help", + "CONTEXT", + "Tools" + }; + + menuItemName = menuItemName.Replace("\\", "/"); + if (acceptableMenuItems.Any(x => menuItemName.ToLower().StartsWith($"{x.ToLower()}/"))) + return true; + + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs.meta new file mode 100644 index 0000000..0e0872b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckFileMenuNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8e3b12ecc1fcd74d9a9f8d2b549fc63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs new file mode 100644 index 0000000..4beec16 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs @@ -0,0 +1,79 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Data.MessageActions; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckLODs : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckLODs(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var prefabs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Prefab); + var badPrefabs = new Dictionary>(); + + foreach (var p in prefabs) + { + var meshFilters = p.GetComponentsInChildren(true); + var badMeshFilters = new List(); + var lodGroups = p.GetComponentsInChildren(true); + + foreach (var mf in meshFilters) + { + if (mf.name.Contains("LOD") && !IsPartOfLodGroup(mf, lodGroups)) + badMeshFilters.Add(mf); + } + + if (badMeshFilters.Count > 0) + badPrefabs.Add(p, badMeshFilters); + } + + if (badPrefabs.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found prefabs are meeting the LOD requirements!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following prefabs do not meet the LOD requirements"); + + foreach (var p in badPrefabs) + { + var resultList = new List(); + resultList.Add(p.Key); + resultList.AddRange(p.Value); + result.AddMessage($"{p.Key.name}.prefab", new OpenAssetAction(p.Key), resultList.ToArray()); + } + + return result; + } + + private bool IsPartOfLodGroup(MeshFilter mf, LODGroup[] lodGroups) + { + foreach (var lodGroup in lodGroups) + { + // If MeshFilter is a child/deep child of a LodGroup AND is referenced in this LOD group - it is valid + if (mf.transform.IsChildOf(lodGroup.transform) && + lodGroup.GetLODs().Any(lod => lod.renderers.Any(renderer => renderer != null && renderer.gameObject == mf.gameObject))) + return true; + } + + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs.meta new file mode 100644 index 0000000..060c427 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLODs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 43b2158602f87704fa7b91561cfc8678 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs new file mode 100644 index 0000000..3bb574e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs @@ -0,0 +1,77 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckLineEndings : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckLineEndings(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var scripts = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.MonoScript); + + var affectedScripts = new ConcurrentBag(); + var scriptContents = new ConcurrentDictionary(); + + // A separate dictionary is needed because MonoScript contents cannot be accessed outside of the main thread + foreach (var s in scripts) + if (s != null) + scriptContents.TryAdd(s, s.text); + + Parallel.ForEach(scriptContents, (s) => + { + if (HasInconsistentLineEndings(s.Value)) + affectedScripts.Add(s.Key); + }); + + if (affectedScripts.Count > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following scripts have inconsistent line endings:", null, affectedScripts.ToArray()); + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No scripts with inconsistent line endings were found!"); + } + + return result; + } + + private bool HasInconsistentLineEndings(string text) + { + int crlfEndings = 0; + int lfEndings = 0; + + var split = text.Split(new[] { "\n" }, StringSplitOptions.None); + for (int i = 0; i < split.Length; i++) + { + var line = split[i]; + if (line.EndsWith("\r")) + crlfEndings++; + else if (i != split.Length - 1) + lfEndings++; + } + + if (crlfEndings > 0 && lfEndings > 0) + return true; + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs.meta new file mode 100644 index 0000000..fb52149 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckLineEndings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85885005d1c594f42826de3555e98365 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs new file mode 100644 index 0000000..6b92c9a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs @@ -0,0 +1,106 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckMeshPrefabs : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IMeshUtilityService _meshUtility; + + public CheckMeshPrefabs(GenericTestConfig config, IAssetUtilityService assetUtility, IMeshUtilityService meshUtility) + { + _config = config; + _assetUtility = assetUtility; + _meshUtility = meshUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var usedModelPaths = new List(); + var prefabs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Prefab); + var missingMeshReferencePrefabs = new List(); + + // Get all meshes in existing prefabs and check if prefab has missing mesh references + foreach (var p in prefabs) + { + var meshes = _meshUtility.GetCustomMeshesInObject(p); + foreach (var mesh in meshes) + { + string meshPath = _assetUtility.ObjectToAssetPath(mesh); + usedModelPaths.Add(meshPath); + } + + if (HasMissingMeshReferences(p)) + missingMeshReferencePrefabs.Add(p); + } + + // Get all meshes in existing models + var allModelPaths = GetAllModelMeshPaths(_config.ValidationPaths); + + // Get the list of meshes without prefabs + List unusedModels = allModelPaths.Except(usedModelPaths).ToList(); + + if (unusedModels.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found prefabs have meshes!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + var models = unusedModels.Select(_assetUtility.AssetPathToObject).ToArray(); + result.AddMessage("The following models do not have associated prefabs", null, models); + + if (missingMeshReferencePrefabs.Count > 0) + result.AddMessage("The following prefabs have missing mesh references", null, missingMeshReferencePrefabs.ToArray()); + + return result; + } + + private IEnumerable GetAllModelMeshPaths(string[] validationPaths) + { + var models = _assetUtility.GetObjectsFromAssets(validationPaths, AssetType.Model); + var paths = new List(); + + foreach (var o in models) + { + var m = (GameObject)o; + var modelPath = _assetUtility.ObjectToAssetPath(m); + var assetImporter = _assetUtility.GetAssetImporter(modelPath); + if (assetImporter is UnityEditor.ModelImporter modelImporter) + { + var clips = modelImporter.clipAnimations.Count(); + var meshes = _meshUtility.GetCustomMeshesInObject(m); + + // Only add if the model has meshes and no clips + if (meshes.Any() && clips == 0) + paths.Add(modelPath); + } + } + + return paths; + } + + private bool HasMissingMeshReferences(GameObject go) + { + var meshes = go.GetComponentsInChildren(true); + var skinnedMeshes = go.GetComponentsInChildren(true); + + if (meshes.Length == 0 && skinnedMeshes.Length == 0) + return false; + + if (meshes.Any(x => x.sharedMesh == null) || skinnedMeshes.Any(x => x.sharedMesh == null)) + return true; + + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs.meta new file mode 100644 index 0000000..3419dd1 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMeshPrefabs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c3d0d642ac6a6a48aa124a93dae3734 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs new file mode 100644 index 0000000..6d4b013 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs @@ -0,0 +1,66 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckMissingComponentsinAssets : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckMissingComponentsinAssets(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var assets = GetAllAssetsWithMissingComponents(_config.ValidationPaths); + + if (assets.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No assets have missing components!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following assets contain missing components", null, assets); + + return result; + } + + private GameObject[] GetAllAssetsWithMissingComponents(string[] validationPaths) + { + var missingReferenceAssets = new List(); + var prefabObjects = _assetUtility.GetObjectsFromAssets(validationPaths, AssetType.Prefab); + + foreach (var p in prefabObjects) + { + if (p != null && IsMissingReference(p)) + missingReferenceAssets.Add(p); + } + + return missingReferenceAssets.ToArray(); + } + + private bool IsMissingReference(GameObject asset) + { + var components = asset.GetComponentsInChildren(); + + foreach (var c in components) + { + if (!c) + return true; + } + + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs.meta new file mode 100644 index 0000000..d7e72f3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinAssets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 22d8f814e2363e34ea220736a4042728 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs new file mode 100644 index 0000000..87ef34b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs @@ -0,0 +1,93 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Data.MessageActions; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using SceneAsset = UnityEditor.SceneAsset; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckMissingComponentsinScenes : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private ISceneUtilityService _sceneUtility; + + public CheckMissingComponentsinScenes(GenericTestConfig config, IAssetUtilityService assetUtility, ISceneUtilityService sceneUtility) + { + _config = config; + _assetUtility = assetUtility; + _sceneUtility = sceneUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var originalScenePath = _sceneUtility.CurrentScenePath; + + var scenePaths = _assetUtility.GetAssetPathsFromAssets(_config.ValidationPaths, AssetType.Scene); + foreach (var scenePath in scenePaths) + { + var missingComponentGOs = GetMissingComponentGOsInScene(scenePath); + + if (missingComponentGOs.Count == 0) + continue; + + result.Status = TestResultStatus.VariableSeverityIssue; + var message = $"GameObjects with missing components or prefab references found in {scenePath}.\n\nClick this message to open the Scene and see the affected GameObjects:"; + result.AddMessage(message, new OpenAssetAction(_assetUtility.AssetPathToObject(scenePath)), missingComponentGOs.ToArray()); + } + + _sceneUtility.OpenScene(originalScenePath); + + if (result.Status == TestResultStatus.Undefined) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No missing components were found!"); + } + + return result; + } + + private List GetMissingComponentGOsInScene(string path) + { + var missingComponentGOs = new List(); + + var scene = _sceneUtility.OpenScene(path); + + if (!scene.IsValid()) + { + Debug.LogWarning("Unable to get Scene in " + path); + return new List(); + } + + var rootObjects = scene.GetRootGameObjects(); + + foreach (var obj in rootObjects) + { + missingComponentGOs.AddRange(GetMissingComponentGOs(obj)); + } + + return missingComponentGOs; + } + + private List GetMissingComponentGOs(GameObject root) + { + var missingComponentGOs = new List(); + var rootComponents = root.GetComponents(); + + if (UnityEditor.PrefabUtility.GetPrefabInstanceStatus(root) == UnityEditor.PrefabInstanceStatus.MissingAsset || rootComponents.Any(c => !c)) + { + missingComponentGOs.Add(root); + } + + foreach (Transform child in root.transform) + missingComponentGOs.AddRange(GetMissingComponentGOs(child.gameObject)); + + return missingComponentGOs; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs.meta new file mode 100644 index 0000000..3ff3408 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckMissingComponentsinScenes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 511e76d0ebcb23d40a7b49dda0e2980f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs new file mode 100644 index 0000000..59519cb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs @@ -0,0 +1,64 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckModelImportLogs : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IModelUtilityService _modelUtility; + + public CheckModelImportLogs(GenericTestConfig config, IAssetUtilityService assetUtility, IModelUtilityService modelUtility) + { + _config = config; + _assetUtility = assetUtility; + _modelUtility = modelUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var models = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Model); + var importLogs = _modelUtility.GetImportLogs(models.ToArray()); + + var warningModels = new List(); + var errorModels = new List(); + + foreach (var kvp in importLogs) + { + if (kvp.Value.Any(x => x.Severity == UnityEngine.LogType.Error)) + errorModels.Add(kvp.Key); + if (kvp.Value.Any(x => x.Severity == UnityEngine.LogType.Warning)) + warningModels.Add(kvp.Key); + } + + if (warningModels.Count > 0 || errorModels.Count > 0) + { + if (warningModels.Count > 0) + { + result.Status = TestResultStatus.Warning; + result.AddMessage("The following models contain import warnings:", null, warningModels.ToArray()); + } + + if (errorModels.Count > 0) + { + result.Status = TestResultStatus.Warning; + result.AddMessage("The following models contain import errors:", null, errorModels.ToArray()); + } + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No issues were detected when importing your models!"); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs.meta new file mode 100644 index 0000000..884b6cb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelImportLogs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98f3ec209166855408eaf4abe5bff591 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs new file mode 100644 index 0000000..cf871c5 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs @@ -0,0 +1,71 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckModelOrientation : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IMeshUtilityService _meshUtility; + + public CheckModelOrientation(GenericTestConfig config, IAssetUtilityService assetUtility, IMeshUtilityService meshUtility) + { + _config = config; + _assetUtility = assetUtility; + _meshUtility = meshUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var models = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Model); + var badModels = new List(); + + foreach (var m in models) + { + var meshes = _meshUtility.GetCustomMeshesInObject(m); + var assetImporter = _assetUtility.GetAssetImporter(m); + + if (!(assetImporter is UnityEditor.ModelImporter modelImporter)) + continue; + + var clips = modelImporter.clipAnimations.Length; + + // Only check if the model has meshes and no clips + if (!meshes.Any() || clips != 0) + continue; + + Transform[] transforms = m.GetComponentsInChildren(true); + + foreach (var t in transforms) + { + var hasMeshComponent = t.TryGetComponent(out _) || t.TryGetComponent(out _); + + if (t.localRotation == Quaternion.identity || !hasMeshComponent) + continue; + + badModels.Add(m); + break; + } + } + + if (badModels.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found models are facing the right way!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following models have incorrect rotation", null, badModels.ToArray()); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs.meta new file mode 100644 index 0000000..7e8243e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelOrientation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56cdcdc41a80fbc46b5b2b83ec8d66d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs new file mode 100644 index 0000000..edf90e2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs @@ -0,0 +1,58 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckModelTypes : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckModelTypes(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var allowedExtensions = new string[] { ".fbx", ".dae", ".abc", ".obj" }; + // Should retrieve All assets and not models here since ADB will not recognize certain + // types if appropriate software is not installed (e.g. .blend without Blender) + var allAssetPaths = _assetUtility.GetAssetPathsFromAssets(_config.ValidationPaths, AssetType.All); + var badModels = new List(); + + foreach (var assetPath in allAssetPaths) + { + var importer = _assetUtility.GetAssetImporter(assetPath); + if (importer == null || !(importer is ModelImporter)) + continue; + + if (allowedExtensions.Any(x => importer.assetPath.ToLower().EndsWith(x))) + continue; + + badModels.Add(_assetUtility.AssetPathToObject(assetPath)); + } + + if (badModels.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All models are of allowed formats!"); + } + else + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following models are of formats that should not be used for Asset Store packages:", null, badModels.ToArray()); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs.meta new file mode 100644 index 0000000..6f45fa7 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckModelTypes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 428b1fb838e6f5a469bbfd26ca3fbfd2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs new file mode 100644 index 0000000..b991eb3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs @@ -0,0 +1,94 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckNormalMapTextures : ITestScript + { + public const int TextureCacheLimit = 8; + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckNormalMapTextures(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var materials = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Material); + var badTextures = new List(); + var badPaths = new List(); + + foreach (var mat in materials) + { + for (int i = 0; i < mat.shader.GetPropertyCount(); i++) + { + if ((mat.shader.GetPropertyFlags(i) & UnityEngine.Rendering.ShaderPropertyFlags.Normal) != 0) + { + var propertyName = mat.shader.GetPropertyName(i); + var assignedTexture = mat.GetTexture(propertyName); + + if (assignedTexture == null) + continue; + + var texturePath = _assetUtility.ObjectToAssetPath(assignedTexture); + var textureImporter = _assetUtility.GetAssetImporter(texturePath) as TextureImporter; + if (textureImporter == null) + continue; + + if (textureImporter.textureType != TextureImporterType.NormalMap && !badTextures.Contains(assignedTexture)) + { + if (badTextures.Count < TextureCacheLimit) + { + badTextures.Add(assignedTexture); + } + else + { + string path = AssetDatabase.GetAssetPath(assignedTexture); + badPaths.Add(path); + } + } + } + } + + EditorUtility.UnloadUnusedAssetsImmediate(); + } + + if (badTextures.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All normal map textures have the correct texture type!"); + } + else if (badPaths.Count != 0) + { + foreach (Texture texture in badTextures) + { + string path = AssetDatabase.GetAssetPath(texture); + badPaths.Add(path); + } + + string paths = string.Join("\n", badPaths); + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following textures are not set to type 'Normal Map'", null); + result.AddMessage(paths); + } + else + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following textures are not set to type 'Normal Map'", null, badTextures.ToArray()); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs.meta new file mode 100644 index 0000000..f996702 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckNormalMapTextures.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d55cea510248f814eb2194c2b53f88d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs new file mode 100644 index 0000000..a6c69fd --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs @@ -0,0 +1,295 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckPackageNaming : ITestScript + { + private const string ForbiddenCharacters = "~`!@#$%^&*()-_=+[{]}\\|;:'\",<>?/"; + private readonly string[] PathsToCheckForForbiddenCharacters = new string[] + { + "Assets/", + "Assets/Editor/", + "Assets/Plugins/", + "Assets/Resources/", + "Assets/StreamingAssets/", + "Assets/WebGLTemplates/" + }; + + private class PathCheckResult + { + public Object[] InvalidMainPaths; + public Object[] InvalidMainPathContentPaths; + public Object[] InvalidMainPathLeadingUpPaths; + public Object[] InvalidHybridPackages; + public Object[] PotentiallyInvalidContent; + + public bool HasIssues => InvalidMainPaths.Length > 0 + || InvalidMainPathContentPaths.Length > 0 + || InvalidMainPathLeadingUpPaths.Length > 0 + || InvalidHybridPackages.Length > 0 + || PotentiallyInvalidContent.Length > 0; + } + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + // Constructor also accepts dependency injection of registered IValidatorService types + public CheckPackageNaming(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var checkResult = GetInvalidPathsInAssets(); + + if (checkResult.HasIssues) + { + result.Status = TestResultStatus.Warning; + + if (checkResult.InvalidMainPaths.Length > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following assets appear to be artificially bumped up in the project hierarchy within commonly used folders", null, checkResult.InvalidMainPaths); + } + + if (checkResult.InvalidMainPathContentPaths.Length > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following assets appear to be artificially bumped up in the project hierarchy within commonly used folders", null, checkResult.InvalidMainPathContentPaths); + } + + if (checkResult.InvalidMainPathLeadingUpPaths.Length > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("Despite not being directly validated, this path would be automatically created by the Unity Importer when importing your package", null, checkResult.InvalidMainPathLeadingUpPaths); + } + + if (checkResult.InvalidHybridPackages.Length > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following packages appear to be artificially bumped up in the Package hierarchy with their 'Display Name' configuration", null, checkResult.InvalidHybridPackages); + } + + if (checkResult.PotentiallyInvalidContent.Length > 0) + { + // Do not override previously set severities + result.AddMessage("It is recommended that nested package content refrains from starting with a special character", null, checkResult.PotentiallyInvalidContent); + } + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All package asset names are valid!"); + } + + return result; + } + + private PathCheckResult GetInvalidPathsInAssets() + { + var allInvalidMainPaths = new List(); + var allInvalidMainContentPaths = new List(); + var allInvalidMainLeadingUpPaths = new List(); + var allInvalidPackagePaths = new List(); + var allInvalidOtherContentPaths = new List(); + + foreach (var validationPath in _config.ValidationPaths) + { + // Is path itself not forbidden e.g.: when validating Assets/_Package, the folder _Package would be invalid + if (!IsDirectMainPathValid(validationPath)) + allInvalidMainPaths.Add(validationPath); + + // Are path contents not forbidden e.g.: when validating Assets/, the folder _Package would be invalid + if (!IsDirectMainPathContentValid(validationPath, out var invalidContentPaths)) + allInvalidMainContentPaths.AddRange(invalidContentPaths); + + // Is the path leading up to this path not forbidden e.g: when validating Assets/_WorkDir/Package, the folder _Workdir would be invalid + if (!IsPathLeadingUpToMainPathValid(validationPath, out var invalidLeadingUpPath)) + allInvalidMainLeadingUpPaths.Add(invalidLeadingUpPath); + + // Is the path pointing to a package valid, e.g.: when validating Packages/com.company.product its display name _Product would be invalid + if (!IsHybridPackageMainPathValid(validationPath, out string invalidPackagePath)) + allInvalidPackagePaths.Add(invalidPackagePath); + } + + var ignoredPaths = new List(); + ignoredPaths.AddRange(allInvalidMainPaths); + ignoredPaths.AddRange(allInvalidMainContentPaths); + ignoredPaths.AddRange(allInvalidMainLeadingUpPaths); + ignoredPaths.AddRange(allInvalidPackagePaths); + + // Mark any other paths that start with a forbidden character + if (!ArePackageContentsValid(ignoredPaths, out var invalidContents)) + allInvalidOtherContentPaths.AddRange(invalidContents); + + return new PathCheckResult() + { + InvalidMainPaths = PathsToObjects(allInvalidMainPaths), + InvalidMainPathContentPaths = PathsToObjects(allInvalidMainContentPaths), + InvalidMainPathLeadingUpPaths = PathsToObjects(allInvalidMainLeadingUpPaths), + InvalidHybridPackages = PathsToObjects(allInvalidPackagePaths), + PotentiallyInvalidContent = PathsToObjects(allInvalidOtherContentPaths) + }; + } + + private bool IsDirectMainPathValid(string validationPath) + { + foreach (var forbiddenPath in PathsToCheckForForbiddenCharacters) + { + var forbiddenPathWithSeparator = forbiddenPath.EndsWith("/") ? forbiddenPath : forbiddenPath + "/"; + if (!validationPath.StartsWith(forbiddenPathWithSeparator)) + continue; + + var truncatedPath = validationPath.Remove(0, forbiddenPathWithSeparator.Length); + var truncatedPathSplit = truncatedPath.Split('/'); + + // It is not a direct main path if it has deeper paths + if (truncatedPathSplit.Length != 1) + continue; + + if (ForbiddenCharacters.Any(x => truncatedPath.StartsWith(x.ToString()))) + return false; + } + + return true; + } + + private bool IsDirectMainPathContentValid(string validationPath, out List invalidContentPaths) + { + invalidContentPaths = new List(); + + var contents = Directory.EnumerateFileSystemEntries(validationPath, "*", SearchOption.AllDirectories) + .Where(x => !x.EndsWith(".meta")) + .Select(GetAdbPath); + + foreach (var contentPath in contents) + { + foreach (var forbiddenPath in PathsToCheckForForbiddenCharacters) + { + var forbiddenPathWithSeparator = forbiddenPath.EndsWith("/") ? forbiddenPath : forbiddenPath + "/"; + if (!contentPath.StartsWith(forbiddenPathWithSeparator)) + continue; + + var truncatedPath = contentPath.Remove(0, forbiddenPathWithSeparator.Length); + var truncatedPathSplit = truncatedPath.Split('/'); + + // Only check the first level of content relative to the forbidden path + if (truncatedPathSplit.Length > 1) + continue; + + if (ForbiddenCharacters.Any(x => truncatedPathSplit[0].StartsWith(x.ToString()))) + invalidContentPaths.Add(contentPath); + } + } + + return invalidContentPaths.Count == 0; + } + + private bool IsPathLeadingUpToMainPathValid(string validationPath, out string invalidLeadingUpPath) + { + invalidLeadingUpPath = string.Empty; + + foreach (var forbiddenPath in PathsToCheckForForbiddenCharacters) + { + var forbiddenPathWithSeparator = forbiddenPath.EndsWith("/") ? forbiddenPath : forbiddenPath + "/"; + if (!validationPath.StartsWith(forbiddenPathWithSeparator)) + continue; + + var truncatedPath = validationPath.Remove(0, forbiddenPathWithSeparator.Length); + var truncatedPathSplit = truncatedPath.Split('/'); + + // It is not a leading up path if it has no deeper path + if (truncatedPathSplit.Length == 1) + continue; + + if (ForbiddenCharacters.Any(x => truncatedPathSplit[0].StartsWith(x.ToString()))) + { + invalidLeadingUpPath = forbiddenPathWithSeparator + truncatedPathSplit[0]; + return false; + } + } + + return true; + } + + private bool IsHybridPackageMainPathValid(string validationPath, out string invalidPackagePath) + { + invalidPackagePath = string.Empty; + + if (!PackageUtility.GetPackageByManifestPath($"{validationPath}/package.json", out var package)) + return true; + + var packageName = package.displayName; + if (ForbiddenCharacters.Any(x => packageName.StartsWith(x.ToString()))) + { + invalidPackagePath = validationPath; + return false; + } + + return true; + } + + private bool ArePackageContentsValid(IEnumerable ignoredPaths, out List invalidContentPaths) + { + invalidContentPaths = new List(); + + foreach (var validationPath in _config.ValidationPaths) + { + var validationPathFolderName = validationPath.Split('/').Last(); + if (!ignoredPaths.Contains(validationPath) && ForbiddenCharacters.Any(x => validationPathFolderName.StartsWith(x.ToString()))) + invalidContentPaths.Add(validationPath); + + var contents = Directory.EnumerateFileSystemEntries(validationPath, "*", SearchOption.AllDirectories) + .Where(x => !x.EndsWith(".meta")) + .Select(GetAdbPath); + + foreach (var contentEntry in contents) + { + if (ignoredPaths.Contains(contentEntry)) + continue; + + var entryName = contentEntry.Split('/').Last(); + if (ForbiddenCharacters.Any(x => entryName.StartsWith(x.ToString()))) + invalidContentPaths.Add(contentEntry); + } + } + + return invalidContentPaths.Count == 0; + } + + private string GetAdbPath(string path) + { + path = path.Replace("\\", "/"); + if (path.StartsWith(Constants.RootProjectPath)) + path = path.Remove(Constants.RootProjectPath.Length); + + return path; + } + + private Object[] PathsToObjects(IEnumerable paths) + { + var objects = new List(); + + foreach (var path in paths) + { + var obj = _assetUtility.AssetPathToObject(path); + if (obj != null) + objects.Add(obj); + } + + return objects.ToArray(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs.meta new file mode 100644 index 0000000..bf1c234 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPackageNaming.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: afe9e04825c7d904981a54404b222290 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs new file mode 100644 index 0000000..03fdecb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs @@ -0,0 +1,76 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Data.MessageActions; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckParticleSystems : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private ISceneUtilityService _sceneUtility; + + public CheckParticleSystems(GenericTestConfig config, IAssetUtilityService assetUtility, ISceneUtilityService sceneUtility) + { + _config = config; + _assetUtility = assetUtility; + _sceneUtility = sceneUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var originalScenePath = _sceneUtility.CurrentScenePath; + + var scenePaths = _assetUtility.GetAssetPathsFromAssets(_config.ValidationPaths, AssetType.Scene); + + foreach (var path in scenePaths) + { + var badParticleSystems = new List(); + + var scene = _sceneUtility.OpenScene(path); + + if (!scene.IsValid()) + { + Debug.LogWarning("Unable to get Scene in " + path); + continue; + } + +#if UNITY_2023_1_OR_NEWER + var particleSystems = GameObject.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); +#else + var particleSystems = GameObject.FindObjectsOfType(); +#endif + + foreach (var ps in particleSystems) + { + if (PrefabUtility.IsPartOfAnyPrefab(ps.gameObject)) + continue; + badParticleSystems.Add(ps); + } + + if (badParticleSystems.Count == 0) + continue; + + result.Status = TestResultStatus.VariableSeverityIssue; + var message = $"Particle Systems not belonging to any Prefab were found in {path}.\n\nClick this message to open the Scene and see the affected Particle Systems:"; + result.AddMessage(message, new OpenAssetAction(AssetDatabase.LoadAssetAtPath(path)), badParticleSystems.ToArray()); + } + + _sceneUtility.OpenScene(originalScenePath); + + if (result.Status == TestResultStatus.Undefined) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No Particle Systems without Prefabs were found!"); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs.meta new file mode 100644 index 0000000..66a1881 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckParticleSystems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a623f7988c75884bb17b169ccd3e993 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs new file mode 100644 index 0000000..9ee5cab --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs @@ -0,0 +1,98 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using AssetStoreTools.Validator.Utility; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckPathLengths : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckPathLengths(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + TestResult result = new TestResult(); + + int pathLengthLimit = 140; + // Get all project paths and sort by length so that folders always come before files + var allPaths = ValidatorUtility.GetProjectPaths(_config.ValidationPaths).OrderBy(x => x.Length); + + var filteredDirs = new Dictionary(); + var filteredFiles = new Dictionary(); + + foreach (var path in allPaths) + { + // Truncated path examples: + // Assets/[Scenes/SampleScene.unity] + // Packages/com.example.package/[Editor/EditorScript.cs] + var truncatedPath = path; + if (path.StartsWith("Assets/")) + truncatedPath = path.Remove(0, "Assets/".Length); + else if (path.StartsWith("Packages/")) + { + var splitPath = path.Split('/'); + truncatedPath = string.Join("/", splitPath.Skip(2)); + } + + // Skip paths under the character limit + if (truncatedPath.Length < pathLengthLimit) + continue; + + // Skip children of already added directories + if (filteredDirs.Keys.Any(x => truncatedPath.StartsWith(x))) + continue; + + if (AssetDatabase.IsValidFolder(path)) + { + filteredDirs.Add(truncatedPath, _assetUtility.AssetPathToObject(path)); + continue; + } + + if (!filteredFiles.ContainsKey(truncatedPath)) + filteredFiles.Add(truncatedPath, _assetUtility.AssetPathToObject(path)); + } + + if (filteredDirs.Count == 0 && filteredFiles.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All package content matches the path limit criteria!"); + return result; + } + + if (filteredDirs.Count > 0) + { + var maxDirLength = filteredDirs.Keys.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur); + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage($"The following folders exceed the path length limit:"); + foreach (var kvp in filteredDirs) + { + result.AddMessage($"Path length: {kvp.Key.Length} characters", null, kvp.Value); + } + } + + if (filteredFiles.Count > 0) + { + var maxFileLength = filteredFiles.Keys.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur); + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage($"The following files exceed the path length limit:"); + foreach (var kvp in filteredFiles) + { + result.AddMessage($"Path length: {kvp.Key.Length} characters", null, kvp.Value); + } + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs.meta new file mode 100644 index 0000000..ff8f8ed --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPathLengths.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ae379305e9165e84584373a8272c09e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs new file mode 100644 index 0000000..5202337 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs @@ -0,0 +1,71 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckPrefabTransforms : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IMeshUtilityService _meshUtility; + + public CheckPrefabTransforms(GenericTestConfig config, IAssetUtilityService assetUtility, IMeshUtilityService meshUtility) + { + _config = config; + _assetUtility = assetUtility; + _meshUtility = meshUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var prefabs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Prefab); + var badPrefabs = new List(); + var badPrefabsLowOffset = new List(); + + foreach (var p in prefabs) + { + var hasRectTransform = p.TryGetComponent(out RectTransform _); + if (hasRectTransform || !_meshUtility.GetCustomMeshesInObject(p).Any()) + continue; + + var positionString = p.transform.position.ToString("F12"); + var rotationString = p.transform.rotation.eulerAngles.ToString("F12"); + var localScaleString = p.transform.localScale.ToString("F12"); + + var vectorZeroString = Vector3.zero.ToString("F12"); + var vectorOneString = Vector3.one.ToString("F12"); + + if (positionString != vectorZeroString || rotationString != vectorZeroString || localScaleString != vectorOneString) + { + if (p.transform.position == Vector3.zero && p.transform.rotation.eulerAngles == Vector3.zero && p.transform.localScale == Vector3.one) + badPrefabsLowOffset.Add(p); + else + badPrefabs.Add(p); + } + } + + if (badPrefabs.Count == 0 && badPrefabsLowOffset.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found prefabs were reset!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + if (badPrefabs.Count > 0) + result.AddMessage("The following prefabs' transforms do not fit the requirements", null, badPrefabs.ToArray()); + if (badPrefabsLowOffset.Count > 0) + result.AddMessage("The following prefabs have unusually low transform values, which might not be accurately displayed " + + "in the Inspector window. Please use the 'Debug' Inspector mode to review the Transform component of these prefabs " + + "or reset the Transform components using the right-click context menu", null, badPrefabsLowOffset.ToArray()); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs.meta new file mode 100644 index 0000000..60db269 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckPrefabTransforms.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f712c17a60bf2d049a4e61c8f79e56c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs new file mode 100644 index 0000000..e5a0606 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs @@ -0,0 +1,30 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; +using UnityEditor; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckScriptCompilation : ITestScript + { + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var hasCompilationErrors = EditorUtility.scriptCompilationFailed; + + if (hasCompilationErrors) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("One or more scripts in the project failed to compile.\n" + + "Please check the Console window to see the list of errors"); + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All scripts in the project compiled successfully!"); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs.meta new file mode 100644 index 0000000..47d2849 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckScriptCompilation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59db88f43969db8499299bce7f4fb967 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs new file mode 100644 index 0000000..d6305af --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs @@ -0,0 +1,63 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; +using UnityEditor; +using UnityEngine; +#if !UNITY_2023_1_OR_NEWER +using UnityEngine.Experimental.Rendering; +#endif +#if UNITY_2023_1_OR_NEWER +using UnityEngine.Rendering; +#endif + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckShaderCompilation : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckShaderCompilation(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var shaders = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Shader); + var badShaders = shaders.Where(ShaderHasError).ToArray(); + + if (badShaders.Length > 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following shader files have errors", null, badShaders); + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All found Shaders have no compilation errors!"); + } + + return result; + } + + private bool ShaderHasError(Object obj) + { + switch (obj) + { + case Shader shader: + return ShaderUtil.ShaderHasError(shader); + case ComputeShader shader: + return ShaderUtil.GetComputeShaderMessageCount(shader) > 0; + case RayTracingShader shader: + return ShaderUtil.GetRayTracingShaderMessageCount(shader) > 0; + default: + return false; + } + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs.meta new file mode 100644 index 0000000..43f501a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckShaderCompilation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7abb278a6082bde4391e0779394cb85b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs new file mode 100644 index 0000000..fbe52dc --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs @@ -0,0 +1,57 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckTextureDimensions : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckTextureDimensions(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var textures = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Texture); + var badTextures = new List(); + + foreach (var texture in textures) + { + if (Mathf.IsPowerOfTwo(texture.width) && Mathf.IsPowerOfTwo(texture.height)) + continue; + + var importer = _assetUtility.GetAssetImporter(_assetUtility.ObjectToAssetPath(texture)); + + if (importer == null || !(importer is TextureImporter textureImporter) + || textureImporter.textureType == TextureImporterType.Sprite + || textureImporter.textureType == TextureImporterType.GUI) + continue; + + badTextures.Add(texture); + } + + if (badTextures.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All texture dimensions are a power of 2!"); + } + else + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following texture dimensions are not a power of 2:", null, badTextures.ToArray()); + } + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs.meta new file mode 100644 index 0000000..bc0d1cf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTextureDimensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 073f1dacf3da34d4783140ae9d485d5f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs new file mode 100644 index 0000000..46cf5b3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs @@ -0,0 +1,233 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckTypeNamespaces : ITestScript + { + private readonly string[] ForbiddenNamespaces = new string[] { "Unity" }; + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private IScriptUtilityService _scriptUtility; + + private enum NamespaceEligibility + { + NoNamespace, + Ok, + Forbidden + } + + private class AnalysisResult + { + public Dictionary> TypesWithoutNamespaces; + public Dictionary> ForbiddenNamespaces; + + public bool HasIssues => TypesWithoutNamespaces.Count > 0 + || ForbiddenNamespaces.Count > 0; + } + + public CheckTypeNamespaces(GenericTestConfig config, IAssetUtilityService assetUtility, IScriptUtilityService scriptUtility) + { + _config = config; + _assetUtility = assetUtility; + _scriptUtility = scriptUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var scriptResult = CheckScripts(); + var assemblyResult = CheckAssemblies(); + + if (scriptResult.HasIssues || assemblyResult.HasIssues) + { + result.Status = TestResultStatus.Warning; + + // Error conditions for forbidden namespaces + + if (scriptResult.ForbiddenNamespaces.Count > 0) + { + result.Status = TestResultStatus.Fail; + result.AddMessage("The following scripts contain namespaces starting with a 'Unity' keyword:"); + AddJoinedMessage(result, scriptResult.ForbiddenNamespaces); + } + + if (assemblyResult.ForbiddenNamespaces.Count > 0) + { + result.Status = TestResultStatus.Fail; + result.AddMessage("The following assemblies contain namespaces starting with a 'Unity' keyword:"); + AddJoinedMessage(result, assemblyResult.ForbiddenNamespaces); + } + + // Variable severity conditions for no-namespace types + + if (scriptResult.TypesWithoutNamespaces.Count > 0) + { + if (result.Status != TestResultStatus.Fail) + result.Status = TestResultStatus.VariableSeverityIssue; + + result.AddMessage("The following scripts contain types not nested under a namespace:"); + AddJoinedMessage(result, scriptResult.TypesWithoutNamespaces); + } + + if (assemblyResult.TypesWithoutNamespaces.Count > 0) + { + if (result.Status != TestResultStatus.Fail) + result.Status = TestResultStatus.VariableSeverityIssue; + + result.AddMessage("The following assemblies contain types not nested under a namespace:"); + AddJoinedMessage(result, assemblyResult.TypesWithoutNamespaces); + } + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("All scripts contain valid namespaces!"); + } + + return result; + } + + private AnalysisResult CheckScripts() + { + var scripts = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.MonoScript).ToArray(); + var scriptNamespaces = _scriptUtility.GetTypeNamespacesFromScriptAssets(scripts); + + var scriptsWithoutNamespaces = new Dictionary>(); + var scriptsWithForbiddenNamespaces = new Dictionary>(); + + foreach (var kvp in scriptNamespaces) + { + var scriptAsset = kvp.Key; + var typesInScriptAsset = kvp.Value; + + var typesWithoutNamespace = new List(); + var discouragedNamespaces = new List(); + var forbiddenNamespaces = new List(); + + foreach (var t in typesInScriptAsset) + { + var eligibility = CheckNamespaceEligibility(t.Namespace); + + switch (eligibility) + { + case NamespaceEligibility.NoNamespace: + typesWithoutNamespace.Add(t.Name); + break; + case NamespaceEligibility.Forbidden: + if (!forbiddenNamespaces.Contains(t.Namespace)) + forbiddenNamespaces.Add(t.Namespace); + break; + case NamespaceEligibility.Ok: + break; + } + } + + if (typesWithoutNamespace.Count > 0) + scriptsWithoutNamespaces.Add(scriptAsset, typesWithoutNamespace); + + if (forbiddenNamespaces.Count > 0) + scriptsWithForbiddenNamespaces.Add(scriptAsset, forbiddenNamespaces); + } + + return new AnalysisResult + { + TypesWithoutNamespaces = scriptsWithoutNamespaces, + ForbiddenNamespaces = scriptsWithForbiddenNamespaces + }; + } + + private AnalysisResult CheckAssemblies() + { + var assemblies = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.PrecompiledAssembly).ToList(); + var assemblyTypes = _scriptUtility.GetTypesFromAssemblies(assemblies); + + var assembliesWithoutNamespaces = new Dictionary>(); + var assembliesWithForbiddenNamespaces = new Dictionary>(); + + foreach (var kvp in assemblyTypes) + { + var assemblyAsset = kvp.Key; + var typesInAssembly = kvp.Value; + + var typesWithoutNamespace = new List(); + var discouragedNamespaces = new List(); + var forbiddenNamespaces = new List(); + + foreach (var t in typesInAssembly) + { + var eligibility = CheckNamespaceEligibility(t.Namespace); + + switch (eligibility) + { + case NamespaceEligibility.NoNamespace: + typesWithoutNamespace.Add($"{GetTypeName(t)} {t.Name}"); + break; + case NamespaceEligibility.Forbidden: + if (!forbiddenNamespaces.Contains(t.Namespace)) + forbiddenNamespaces.Add(t.Namespace); + break; + case NamespaceEligibility.Ok: + break; + } + } + + if (typesWithoutNamespace.Count > 0) + assembliesWithoutNamespaces.Add(assemblyAsset, typesWithoutNamespace); + + if (forbiddenNamespaces.Count > 0) + assembliesWithForbiddenNamespaces.Add(assemblyAsset, forbiddenNamespaces); + } + + return new AnalysisResult + { + TypesWithoutNamespaces = assembliesWithoutNamespaces, + ForbiddenNamespaces = assembliesWithForbiddenNamespaces + }; + } + + private NamespaceEligibility CheckNamespaceEligibility(string fullNamespace) + { + if (string.IsNullOrEmpty(fullNamespace)) + return NamespaceEligibility.NoNamespace; + + var split = fullNamespace.Split('.'); + var topLevelNamespace = split[0]; + if (ForbiddenNamespaces.Any(x => topLevelNamespace.StartsWith(x, StringComparison.OrdinalIgnoreCase))) + return NamespaceEligibility.Forbidden; + + return NamespaceEligibility.Ok; + } + + private string GetTypeName(Type type) + { + if (type.IsClass) + return "class"; + if (type.IsInterface) + return "interface"; + if (type.IsEnum) + return "enum"; + if (type.IsValueType) + return "struct"; + + throw new ArgumentException($"Received an unrecognizable type {type}. Type must be either a class, interface, struct or enum"); + } + + private void AddJoinedMessage(TestResult result, Dictionary> assetsWithMessages) + { + foreach (var kvp in assetsWithMessages) + { + var message = string.Join("\n", kvp.Value); + result.AddMessage(message, null, kvp.Key); + } + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs.meta new file mode 100644 index 0000000..e18155a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/CheckTypeNamespaces.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 279249fa7ef8c2446b3a9f013eeedbf0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs new file mode 100644 index 0000000..ce71c8c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveExecutableFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveExecutableFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var executables = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Executable).ToArray(); + + if (executables.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No executable files were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following executable files were found", null, executables); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs.meta new file mode 100644 index 0000000..04899bc --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveExecutableFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e4450592cc60e54286ad089b66db94d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs new file mode 100644 index 0000000..4860bbb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveJPGFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveJPGFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var jpgs = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.JPG).ToArray(); + + if (jpgs.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No JPG/JPEG textures were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following textures are compressed as JPG/JPEG", null, jpgs); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs.meta new file mode 100644 index 0000000..bc21513 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJPGFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5634a12b3a8544c4585bbc280ae59ce2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs new file mode 100644 index 0000000..1996f38 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveJavaScriptFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveJavaScriptFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var javascriptObjects = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.JavaScript).ToArray(); + + if (javascriptObjects.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No UnityScript / JS files were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following assets are UnityScript / JS files", null, javascriptObjects); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs.meta new file mode 100644 index 0000000..791abf8 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveJavaScriptFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab1676bde9afba442b35fd3319c18063 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs new file mode 100644 index 0000000..84fc911 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs @@ -0,0 +1,83 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveLossyAudioFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveLossyAudioFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + string SanitizeForComparison(UnityObject o) + { + Regex alphanumericRegex = new Regex("[^a-zA-Z0-9]"); + string path = _assetUtility.ObjectToAssetPath(o); + path = path.ToLower(); + + int extensionIndex = path.LastIndexOf('.'); + string extension = path.Substring(extensionIndex + 1); + string sanitized = path.Substring(0, extensionIndex); + + int separatorIndex = sanitized.LastIndexOf('/'); + sanitized = sanitized.Substring(separatorIndex); + sanitized = alphanumericRegex.Replace(sanitized, String.Empty); + sanitized = sanitized.Replace(extension, String.Empty); + sanitized = sanitized.Trim(); + + return sanitized; + } + + var lossyAudioObjects = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.LossyAudio).ToArray(); + if (lossyAudioObjects.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No lossy audio files were found!"); + return result; + } + + // Try to find and match variants + var nonLossyAudioObjects = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.NonLossyAudio); + HashSet nonLossyPathSet = new HashSet(); + foreach (var asset in nonLossyAudioObjects) + { + var path = SanitizeForComparison(asset); + nonLossyPathSet.Add(path); + } + + var unmatchedAssets = new List(); + foreach (var asset in lossyAudioObjects) + { + var path = SanitizeForComparison(asset); + if (!nonLossyPathSet.Contains(path)) + unmatchedAssets.Add(asset); + } + + if (unmatchedAssets.Count == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No lossy audio files were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following lossy audio files were found without identically named non-lossy variants:", null, unmatchedAssets.ToArray()); + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs.meta new file mode 100644 index 0000000..729af7f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveLossyAudioFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7205a85061273a4eb50586f13f35d35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs new file mode 100644 index 0000000..04c2069 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveMixamoFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveMixamoFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var mixamoFiles = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Mixamo).ToArray(); + + if (mixamoFiles.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No Mixamo files were found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following Mixamo files were found", null, mixamoFiles); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs.meta new file mode 100644 index 0000000..7a1e4f8 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveMixamoFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9df432e52aa958b44bb5e20c13d16552 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs new file mode 100644 index 0000000..38c2c26 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveSpeedTreeFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveSpeedTreeFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var speedtreeObjects = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.SpeedTree).ToArray(); + + if (speedtreeObjects.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No SpeedTree assets have been found!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following SpeedTree assets have been found", null, speedtreeObjects); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs.meta new file mode 100644 index 0000000..855dbe2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveSpeedTreeFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e06bb7e0aa4f9944abc18281c002dff4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs new file mode 100644 index 0000000..25e69f2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs @@ -0,0 +1,38 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class RemoveVideoFiles : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public RemoveVideoFiles(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var videos = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.Video).ToArray(); + + if (videos.Length == 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No video files were found, looking good!"); + return result; + } + + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("The following video files were found", null, videos); + + return result; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs.meta new file mode 100644 index 0000000..98aea88 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/Generic/RemoveVideoFiles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f99724c71b0de66419b5d6e8e9bfcc2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage.meta new file mode 100644 index 0000000..b8fde81 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 016d62b2cd8346a49815615efd1d6e39 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs new file mode 100644 index 0000000..e5ce2cc --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs @@ -0,0 +1,172 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityObject = UnityEngine.Object; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckDemoScenes : ITestScript + { + private class DemoSceneScanResult + { + public List ValidAdbScenes; + public List HybridScenePaths; + public List NestedUnityPackages; + } + + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + private ISceneUtilityService _sceneUtility; + + public CheckDemoScenes(GenericTestConfig config, IAssetUtilityService assetUtility, ISceneUtilityService sceneUtility) + { + _config = config; + _assetUtility = assetUtility; + _sceneUtility = sceneUtility; + } + + public TestResult Run() + { + var result = new TestResult(); + var demoSceneScanResult = CheckForDemoScenes(_config); + + // Valid demo scenes were found in ADB + if (demoSceneScanResult.ValidAdbScenes.Count > 0) + { + result.Status = TestResultStatus.Pass; + result.AddMessage("Demo scenes found", null, demoSceneScanResult.ValidAdbScenes.ToArray()); + return result; + } + + // Valid demo scenes found in UPM package.json + if (demoSceneScanResult.HybridScenePaths.Count > 0) + { + result.Status = TestResultStatus.Pass; + + var upmSampleSceneList = string.Join("\n-", demoSceneScanResult.HybridScenePaths); + upmSampleSceneList = upmSampleSceneList.Insert(0, "-"); + + result.AddMessage($"Demo scenes found:\n{upmSampleSceneList}"); + return result; + } + + // No valid scenes found, but package contains nested .unitypackages + if (demoSceneScanResult.NestedUnityPackages.Count > 0) + { + result.Status = TestResultStatus.Warning; + result.AddMessage("Could not find any valid Demo scenes in the selected validation paths."); + result.AddMessage("The following nested .unitypackage files were found. " + + "If they contain any demo scenes, you can ignore this warning.", null, demoSceneScanResult.NestedUnityPackages.ToArray()); + return result; + } + + // No valid scenes were found and there is nothing pointing to their inclusion in the package + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("Could not find any valid Demo Scenes in the selected validation paths."); + return result; + } + + private DemoSceneScanResult CheckForDemoScenes(GenericTestConfig config) + { + var scanResult = new DemoSceneScanResult(); + scanResult.ValidAdbScenes = CheckForDemoScenesInAssetDatabase(config); + scanResult.HybridScenePaths = CheckForDemoScenesInUpmSamples(config); + scanResult.NestedUnityPackages = CheckForNestedUnityPackages(config); + + return scanResult; + } + + private List CheckForDemoScenesInAssetDatabase(GenericTestConfig config) + { + var scenePaths = _assetUtility.GetAssetPathsFromAssets(config.ValidationPaths, AssetType.Scene).ToArray(); + if (scenePaths.Length == 0) + return new List(); + + var originalScenePath = _sceneUtility.CurrentScenePath; + var validScenePaths = scenePaths.Where(CanBeDemoScene).ToArray(); + _sceneUtility.OpenScene(originalScenePath); + + if (validScenePaths.Length == 0) + return new List(); + + return validScenePaths.Select(x => AssetDatabase.LoadAssetAtPath(x)).ToList(); + } + + private bool CanBeDemoScene(string scenePath) + { + // Check skybox + var sceneSkyboxPath = _assetUtility.ObjectToAssetPath(RenderSettings.skybox).Replace("\\", "").Replace("/", ""); + var defaultSkyboxPath = "Resources/unity_builtin_extra".Replace("\\", "").Replace("/", ""); + + if (!sceneSkyboxPath.Equals(defaultSkyboxPath, StringComparison.OrdinalIgnoreCase)) + return true; + + // Check GameObjects + _sceneUtility.OpenScene(scenePath); + var rootObjects = _sceneUtility.GetRootGameObjects(); + var count = rootObjects.Length; + + if (count == 0) + return false; + + if (count != 2) + return true; + + var cameraGOUnchanged = rootObjects.Any(o => o.TryGetComponent(out _) && o.GetComponents(typeof(Component)).Length == 3); + var lightGOUnchanged = rootObjects.Any(o => o.TryGetComponent(out _) && o.GetComponents(typeof(Component)).Length == 2); + + return !cameraGOUnchanged || !lightGOUnchanged; + } + + private List CheckForDemoScenesInUpmSamples(GenericTestConfig config) + { + var scenePaths = new List(); + + foreach (var path in config.ValidationPaths) + { + if (!File.Exists($"{path}/package.json")) + continue; + + var packageJsonText = File.ReadAllText($"{path}/package.json"); + var json = JObject.Parse(packageJsonText); + + if (!json.ContainsKey("samples") || json["samples"].Type != JTokenType.Array || json["samples"].ToList().Count == 0) + continue; + + foreach (var sample in json["samples"].ToList()) + { + var samplePath = sample["path"].ToString(); + samplePath = $"{path}/{samplePath}"; + if (!Directory.Exists(samplePath)) + continue; + + var sampleScenePaths = Directory.GetFiles(samplePath, "*.unity", SearchOption.AllDirectories); + foreach (var scenePath in sampleScenePaths) + { + // If meta file is not found, the sample will not be included with the exported .unitypackage + if (!File.Exists($"{scenePath}.meta")) + continue; + + if (!scenePaths.Contains(scenePath.Replace("\\", "/"))) + scenePaths.Add(scenePath.Replace("\\", "/")); + } + } + } + + return scenePaths; + } + + private List CheckForNestedUnityPackages(GenericTestConfig config) + { + var unityPackages = _assetUtility.GetObjectsFromAssets(config.ValidationPaths, AssetType.UnityPackage).ToArray(); + return unityPackages.ToList(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs.meta new file mode 100644 index 0000000..2707290 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDemoScenes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f844c2dfa4669ff4eacf5591b544edaf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs new file mode 100644 index 0000000..75ec7e3 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs @@ -0,0 +1,73 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System; +using System.IO; +using System.Linq; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckDocumentation : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + public CheckDocumentation(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var textFilePaths = _assetUtility.GetAssetPathsFromAssets(_config.ValidationPaths, AssetType.Documentation).ToArray(); + var documentationFilePaths = textFilePaths.Where(CouldBeDocumentation).ToArray(); + + if (textFilePaths.Length == 0) + { + result.Status = TestResultStatus.VariableSeverityIssue; + result.AddMessage("No potential documentation files ('.txt', '.pdf', " + + "'.html', '.rtf', '.md') found within the given path."); + } + else if (documentationFilePaths.Length == 0) + { + result.Status = TestResultStatus.Warning; + var textFileObjects = textFilePaths.Select(_assetUtility.AssetPathToObject).ToArray(); + result.AddMessage("The following files have been found to match the documentation file format," + + " but may not be documentation in content", + null, textFileObjects); + } + else + { + result.Status = TestResultStatus.Pass; + var documentationFileObjects = documentationFilePaths.Select(_assetUtility.AssetPathToObject).ToArray(); + result.AddMessage("Found documentation files", null, documentationFileObjects); + } + + return result; + } + + private bool CouldBeDocumentation(string filePath) + { + if (filePath.EndsWith(".pdf")) + return true; + + using (var fs = File.Open(filePath, FileMode.Open)) + using (var bs = new BufferedStream(fs)) + using (var sr = new StreamReader(bs)) + { + string line; + while ((line = sr.ReadLine()) != null) + { + var mentionsDocumentation = line.IndexOf("documentation", StringComparison.OrdinalIgnoreCase) >= 0; + if (mentionsDocumentation) + return true; + } + } + + return false; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs.meta new file mode 100644 index 0000000..f8cea26 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckDocumentation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c8425198983eda4c9b35aa0d59ea33c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs new file mode 100644 index 0000000..936d776 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs @@ -0,0 +1,69 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; +using System.IO; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckPackageSize : ITestScript + { + private GenericTestConfig _config; + + public CheckPackageSize(GenericTestConfig config) + { + _config = config; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var packageSize = CalculatePackageSize(_config.ValidationPaths); + float packageSizeInGB = packageSize / (1024f * 1024f * 1024f); + float maxPackageSizeInGB = Constants.Uploader.MaxPackageSizeBytes / (1024f * 1024f * 1024f); + + if (packageSizeInGB - maxPackageSizeInGB >= 0.1f) + { + result.Status = TestResultStatus.Warning; + + result.AddMessage($"The uncompressed size of your package ({packageSizeInGB:0.#} GB) exceeds the maximum allowed package size of {maxPackageSizeInGB:0.#} GB. " + + $"Please make sure that the compressed .unitypackage size does not exceed the size limit."); + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("Your package does not exceed the maximum allowed package size!"); + } + + return result; + } + + private long CalculatePackageSize(string[] assetPaths) + { + long totalSize = 0; + + foreach (var path in assetPaths) + { + totalSize += CalculatePathSize(path); + } + + return totalSize; + } + + private long CalculatePathSize(string path) + { + long size = 0; + + var dirInfo = new DirectoryInfo(path); + if (!dirInfo.Exists) + return size; + + foreach (var file in dirInfo.EnumerateFiles()) + size += file.Length; + + foreach (var nestedDir in dirInfo.EnumerateDirectories()) + size += CalculatePathSize(nestedDir.FullName); + + return size; + } + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs.meta new file mode 100644 index 0000000..1909c93 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckPackageSize.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8601b99f4afa5049954f3a2dd5996d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs new file mode 100644 index 0000000..66e4d30 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs @@ -0,0 +1,217 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services.Validation; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator.TestMethods +{ + internal class CheckProjectTemplateAssets : ITestScript + { + private GenericTestConfig _config; + private IAssetUtilityService _assetUtility; + + // Constructor also accepts dependency injection of registered IValidatorService types + public CheckProjectTemplateAssets(GenericTestConfig config, IAssetUtilityService assetUtility) + { + _config = config; + _assetUtility = assetUtility; + } + + public TestResult Run() + { + var result = new TestResult() { Status = TestResultStatus.Undefined }; + + var assets = _assetUtility.GetObjectsFromAssets(_config.ValidationPaths, AssetType.All); + var invalidAssetsByGuid = CheckGuids(assets); + var invalidAssetsByPath = CheckPaths(assets); + + var hasIssues = invalidAssetsByGuid.Length > 0 + || invalidAssetsByPath.Length > 0; + + if (hasIssues) + { + result.Status = TestResultStatus.VariableSeverityIssue; + + if (invalidAssetsByPath.Length > 0) + { + result.AddMessage("The following assets were found to have an asset path which is common to project template asset paths. They should be renamed or moved:", null, invalidAssetsByPath); + } + + if (invalidAssetsByGuid.Length > 0) + { + result.AddMessage("The following assets were found to be using a GUID which is common to project template asset GUIDs. They should be assigned a new GUID:", null, invalidAssetsByGuid); + } + } + else + { + result.Status = TestResultStatus.Pass; + result.AddMessage("No common assets that might cause asset clashing were found!"); + } + + return result; + } + + private Object[] CheckGuids(IEnumerable assets) + { + var clashingAssets = new List(); + foreach (var asset in assets) + { + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out var guid, out long _)) + continue; + + if (CommonTemplateAssets.Any(x => x.Key.Equals(guid, System.StringComparison.OrdinalIgnoreCase))) + clashingAssets.Add(asset); + } + + return clashingAssets.ToArray(); + } + + private Object[] CheckPaths(IEnumerable assets) + { + var clashingAssets = new List(); + foreach (var asset in assets) + { + var assetPath = AssetDatabase.GetAssetPath(asset); + if (CommonTemplateAssets.Any(x => x.Value.Equals(assetPath, System.StringComparison.OrdinalIgnoreCase))) + clashingAssets.Add(asset); + } + + return clashingAssets.ToArray(); + } + + private Dictionary CommonTemplateAssets = new Dictionary() + { + {"3f9215ea0144899419cfbc0957140d3f", "Assets/DefaultVolumeProfile.asset"}, + {"3d4c13846a3e9bd4c8ccfbd0657ed847", "Assets/DefaultVolumeProfile.asset"}, + {"4cee8bca36f2ab74b8feb832747fa6f4", "Assets/Editor/com.unity.mobile.notifications/NotificationSettings.asset"}, + {"45a04f37e0f48c744acc0874c4a8918a", "Assets/Editor/com.unity.mobile.notifications/NotificationSettings.asset"}, + {"54a3a0570aebe8949bec4966f1376581", "Assets/HDRPDefaultResources/DefaultHDRISky.exr"}, + {"e93c35b24eb03c74284e7dc0b755bfcc", "Assets/HDRPDefaultResources/DefaultHDRPAsset.asset"}, + {"254320a857a30444da2c99496a186368", "Assets/HDRPDefaultResources/DefaultLookDevProfile.asset"}, + {"2bfa7b9d63fa79e4abdc033f54a868d2", "Assets/HDRPDefaultResources/DefaultSceneRoot.prefab"}, + {"f9e3ff5a1b8f49c4fa8686e68d2dadae", "Assets/HDRPDefaultResources/DefaultSceneRoot.prefab"}, + {"d87f7d7815073e840834a16a518c1237", "Assets/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset"}, + {"145290c901d58b343bdeb3b4362c9ff2", "Assets/HDRPDefaultResources/DefaultVFXResources.asset"}, + {"acc11144f57719542b5fa25f02e74afb", "Assets/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset"}, + {"582adbd84082fdb4faf7cd4beb1ccd14", "Assets/HDRPDefaultResources/HDRPDefaultSettings.asset"}, + {"2801c2ff7303a7543a8727f862f6c236", "Assets/HDRPDefaultResources/Sky and Fog Settings Profile.asset"}, + {"ea5c25297f0c0a04da0eabb1c26a7509", "Assets/HDRPDefaultResources/SkyFogSettingsProfile.asset"}, + {"3590b91b4603b465dbb4216d601bff33", "Assets/InputSystem_Actions.inputactions"}, + {"289c1b55c9541489481df5cc06664110", "Assets/InputSystem_Actions.inputactions"}, + {"dc70d2c4f369241dd99afd7c451b813e", "Assets/InputSystem_Actions.inputactions"}, + {"2bcd2660ca9b64942af0de543d8d7100", "Assets/InputSystem_Actions.inputactions"}, + {"052faaac586de48259a63d0c4782560b", "Assets/InputSystem_Actions.inputactions"}, + {"35845fe01580c41289b024647b1d1c53", "Assets/InputSystem_Actions.inputactions"}, + {"8124e5870f4fd4c779e7a5f994e84ad1", "Assets/OutdoorsScene.unity"}, + {"2dd802e4d37c65149922028d3e973832", "Assets/Presets/AudioCompressedInMemory.preset"}, + {"e18fd6ecd9cdb524ca99844f39b9d9ac", "Assets/Presets/AudioCompressedInMemory.preset"}, + {"86bcce7f5575b54408aa0f3a7d321039", "Assets/Presets/AudioStreaming.preset"}, + {"460e573eb8466884baaa0b8475505f83", "Assets/Presets/AudioStreaming.preset"}, + {"e8537455c6c08bd4e8bf0be3707da685", "Assets/Presets/Defaults/AlbedoTexture_Default.preset"}, + {"7a99f8aa944efe94cb9bd74562b7d5f9", "Assets/Presets/Defaults/AlbedoTexture_Default.preset"}, + {"0cd792cc87e492d43b4e95b205fc5cc6", "Assets/Presets/Defaults/AudioDecompressOnLoad_Default.preset"}, + {"e7689051185d12f4298e1ebb2693a29f", "Assets/Presets/Defaults/AudioDecompressOnLoad.preset"}, + {"463065d4f17d1d94d848aa127b94dd43", "Assets/Presets/Defaults/DirectionalLight_Default.preset"}, + {"c1cf8506f04ef2c4a88b64b6c4202eea", "Assets/Presets/Defaults/DirectionalLight_Default.preset"}, + {"8fa3055e2a1363246838debd20206d37", "Assets/Presets/Defaults/SSSSettings_Default.preset"}, + {"78830bb1431cab940b74be615e2a739f", "Assets/Presets/HDRTexture.preset"}, + {"14a57cf3b9fa1c74b884aa7e0dcf1faa", "Assets/Presets/NormalTexture.preset"}, + {"1d826a4c23450f946b19c20560595a1f", "Assets/Presets/NormalTexture.preset"}, + {"45f7b2e3c78185248b3adbb14429c2ab", "Assets/Presets/UtilityTexture.preset"}, + {"78fae3569c6c66c46afc3d9d4fb0b8d4", "Assets/Presets/UtilityTexture.preset"}, + {"9303d565bd8aa6948ba775e843320e4d", "Assets/Presets/UtilityTexture.preset"}, + {"34f54ff1ff9415249a847506b6f2fec5", "Assets/Scenes/PrefabEditingScene.unity"}, + {"cbfe36cfddfde964d9dfce63a355d5dd", "Assets/Scenes/samplescene.unity"}, + {"2cda990e2423bbf4892e6590ba056729", "Assets/Scenes/SampleScene.unity"}, + {"9fc0d4010bbf28b4594072e72b8655ab", "Assets/Scenes/SampleScene.unity"}, + {"3db1837cc97a95e4c98610966fac2b0b", "Assets/Scenes/SampleScene.unity"}, + {"3fc8acdd13e6c734bafef6554d6fdbcd", "Assets/Scenes/SampleScene.unity"}, + {"8c9cfa26abfee488c85f1582747f6a02", "Assets/Scenes/SampleScene.unity"}, + {"c850ee8c3b14cc8459e7e186857cf567", "Assets/Scenes/SampleScene.unity"}, + {"99c9720ab356a0642a771bea13969a05", "Assets/Scenes/SampleScene.unity"}, + {"d1c3109bdb54ad54c8a2b2838528e640", "Assets/Scenes/SampleScene.unity"}, + {"477cc4148fad3449482a3bc3178594e2", "Assets/Scenes/SampleSceneLightingSettings.lighting"}, + {"4eb578550bc4f824e97f0a72eac1f3a5", "Assets/Scripts/LookWithMouse.cs"}, + {"87f6dfceb3e39a947a312f7eeaa2a113", "Assets/Scripts/PlayerMovement.cs"}, + {"be76e5f14cfee674cb30b491fb72b09b", "Assets/Scripts/SimpleCameraController.cs"}, + {"6547d18b2bc62c94aa5ec1e87434da4e", "Assets/Scripts/SimpleCameraController.cs"}, + {"e8a636f62116c0a40bbfefdf876d4608", "Assets/Scripts/SimpleCameraController.cs"}, + {"14e519c409be4a1428028347410f5677", "Assets/Scripts/SimpleCameraController.cs"}, + {"a04c28107d77d5e42b7155783b8475b6", "Assets/Settings/Cockpit_Renderer.asset"}, + {"ab09877e2e707104187f6f83e2f62510", "Assets/Settings/DefaultVolumeProfile.asset"}, + {"238cd62f6b58cb04e9c94749c4a015a7", "Assets/Settings/DefaultVolumeProfile.asset"}, + {"5a9a2dc462c7bde4f86d0615a19c2c72", "Assets/Settings/DiffusionProfiles/BambooLeaves.asset"}, + {"78322c7f82657514ebe48203160e3f39", "Assets/Settings/Foliage.asset"}, + {"3e2e6bfc59709614ab90c0cd7d755e48", "Assets/Settings/HDRP Balanced.asset"}, + {"36dd385e759c96147b6463dcd1149c11", "Assets/Settings/HDRP High Fidelity.asset"}, + {"168a2336534e4e043b2a210b6f8d379a", "Assets/Settings/HDRP Performant.asset"}, + {"4594f4a3fb14247e192bcca6dc23c8ed", "Assets/Settings/HDRPDefaultResources/DefaultLookDevProfile.asset"}, + {"14b392ee213d25a48b1feddbd9f5a9be", "Assets/Settings/HDRPDefaultResources/DefaultSettingsVolumeProfile.asset"}, + {"879ffae44eefa4412bb327928f1a96dd", "Assets/Settings/HDRPDefaultResources/FoliageDiffusionProfile.asset"}, + {"b9f3086da92434da0bc1518f19f0ce86", "Assets/Settings/HDRPDefaultResources/HDRenderPipelineAsset.asset"}, + {"ac0316ca287ba459492b669ff1317a6f", "Assets/Settings/HDRPDefaultResources/HDRenderPipelineGlobalSettings.asset"}, + {"48e911a1e337b44e2b85dbc65b47a594", "Assets/Settings/HDRPDefaultResources/SkinDiffusionProfile.asset"}, + {"d03ed43fc9d8a4f2e9fa70c1c7916eb9", "Assets/Settings/Lit2DSceneTemplate.scenetemplate"}, + {"65bc7dbf4170f435aa868c779acfb082", "Assets/Settings/Mobile_Renderer.asset"}, + {"5e6cbd92db86f4b18aec3ed561671858", "Assets/Settings/Mobile_RPAsset.asset"}, + {"23cccccf13c3d4170a9b21e52a9bc86b", "Assets/Settings/Mobile/Mobile_High_ScreenRenderer.asset"}, + {"6e8f76111115f44e0a76c2bff3cec258", "Assets/Settings/Mobile/Mobile_Low_Renderer.asset"}, + {"aed30aee6a3ceae4090dadd1934d2ad0", "Assets/Settings/Mobile/Mobile_Low_ScreenRenderer.asset"}, + {"d7686b11d09df481bac3c76ecc5ea626", "Assets/Settings/Mobile/Mobile_Low.asset"}, + {"f288ae1f4751b564a96ac7587541f7a2", "Assets/Settings/PC_Renderer.asset"}, + {"4b83569d67af61e458304325a23e5dfd", "Assets/Settings/PC_RPAsset.asset"}, + {"42b230d443c6d6c4b89c47f97db59121", "Assets/Settings/PC/PC_High_ScreenRenderer.asset"}, + {"13ba41cd2fa191f43890b271bd110ed9", "Assets/Settings/PC/PC_Low_Renderer.asset"}, + {"a73f6fa069dd14a42b40cbb01bae63b4", "Assets/Settings/PC/PC_Low_ScreenRenderer.asset"}, + {"4eb9ff6b5314098428cfa0be7e36ccda", "Assets/Settings/PC/PC_Low.asset"}, + {"573ac53c334415945bf239de2c2f0511", "Assets/Settings/PlayerControllerFPS.prefab"}, + {"7ba2b06fb32e5274aad88925a5b8d3f5", "Assets/Settings/PostProcessVolumeProfile.asset"}, + {"424799608f7334c24bf367e4bbfa7f9a", "Assets/Settings/Renderer2D.asset"}, + {"183cbd347d25080429f42b520742bbd8", "Assets/Settings/SampleScenePostProcessingSettings.asset"}, + {"10fc4df2da32a41aaa32d77bc913491c", "Assets/Settings/SampleSceneProfile.asset"}, + {"a6560a915ef98420e9faacc1c7438823", "Assets/Settings/SampleSceneProfile.asset"}, + {"a123fc0ac58cb774e8592c925f167e7c", "Assets/Settings/SampleSceneSkyandFogSettings.asset"}, + {"26bdddf49760c61438938733f07fa2a2", "Assets/Settings/Skin.asset"}, + {"8ba92e2dd7f884a0f88b98fa2d235fe7", "Assets/Settings/SkyandFogSettingsProfile.asset"}, + {"4a8e21d5c33334b11b34a596161b9360", "Assets/Settings/UniversalRenderer.asset"}, + {"18dc0cd2c080841dea60987a38ce93fa", "Assets/Settings/UniversalRenderPipelineGlobalSettings.asset"}, + {"bdede76083021864d8ff8bf23b2f37f1", "Assets/Settings/UniversalRenderPipelineGlobalSettings.asset"}, + {"19ba41d7c0026c3459d37c2fe90c55a0", "Assets/Settings/UniversalRP-HighQuality.asset"}, + {"a31e9f9f9c9d4b9429ed0d1234e22103", "Assets/Settings/UniversalRP-LowQuality.asset"}, + {"d847b876476d3d6468f5dfcd34266f96", "Assets/Settings/UniversalRP-MediumQuality.asset"}, + {"681886c5eb7344803b6206f758bf0b1c", "Assets/Settings/UniversalRP.asset"}, + {"e634585d5c4544dd297acaee93dc2beb", "Assets/Settings/URP-Balanced-Renderer.asset"}, + {"e1260c1148f6143b28bae5ace5e9c5d1", "Assets/Settings/URP-Balanced.asset"}, + {"c40be3174f62c4acf8c1216858c64956", "Assets/Settings/URP-HighFidelity-Renderer.asset"}, + {"7b7fd9122c28c4d15b667c7040e3b3fd", "Assets/Settings/URP-HighFidelity.asset"}, + {"707360a9c581a4bd7aa53bfeb1429f71", "Assets/Settings/URP-Performant-Renderer.asset"}, + {"d0e2fc18fe036412f8223b3b3d9ad574", "Assets/Settings/URP-Performant.asset"}, + {"b62413aeefabaaa41a4b5a71dd7ae1ac", "Assets/Settings/VolumeProfiles/CinematicProfile.asset"}, + {"ac0c2cad5778d4544b6a690963e02fe3", "Assets/Settings/VolumeProfiles/DefaultVolumeProfile.asset"}, + {"f2d4d916a6612574cad220d125febbf2", "Assets/Settings/VolumeProfiles/LowQualityVolumeProfile.asset"}, + {"cef078630d63d0442a070f84d4f13735", "Assets/Settings/VolumeProfiles/MarketProfile.asset"}, + {"7ede9c9f109e5c442b7d29e54b4996fc", "Assets/Settings/VolumeProfiles/MediaOverrides.asset"}, + {"3532e98caae428047bcefe69a344f72c", "Assets/Settings/VolumeProfiles/OutlineEnabled.asset"}, + {"bfc08ba7e35de1a44bb84a32f1a693e1", "Assets/Settings/VolumeProfiles/ZenGardenProfile.asset"}, + {"59a34a3881431c246b3564a0f0ca5bb0", "Assets/Settings/Volumes/CinematicPhysicalCamera.asset"}, + {"03bc34b71695890468eb021c73b228db", "Assets/Settings/Volumes/ScreenshotsTimelineProfile.asset"}, + {"7f342610b85f4164f808a1f380dcc668", "Assets/Settings/Volumes/VolumeGlobal.asset"}, + {"bd6d234073408c44ca3828113aac655e", "Assets/Settings/Volumes/VolumeRoom1.asset"}, + {"d78a1b031ab26034eb6ec3cbc9fbcec3", "Assets/Settings/Volumes/VolumeRoom2.asset"}, + {"5727d3e07f75c3744b6cc8a1e55850a9", "Assets/Settings/Volumes/VolumeRoom2Skylight.asset"}, + {"06114ad16a0bc0a41957375ac3bf472e", "Assets/Settings/Volumes/VolumeRoom3.asset"}, + {"1584bf21cf81d5147aa00e8a2deaf2fb", "Assets/Settings/Volumes/VolumeRoom3Corridor.asset"}, + {"7bca3a07cdd522c4c8020832c20b3eae", "Assets/Settings/Volumes/VolumeRoom3Sitting.asset"}, + {"2872d90954412244a8b4a477b939c3ca", "Assets/Settings/XR/Loaders/Mock_HMD_Loader.asset"}, + {"f25758a0f79593d4a9b3ee30a17b4c2e", "Assets/Settings/XR/Loaders/Oculus_Loader.asset"}, + {"9e9f2958d1b4b4642ace1d0c7770650b", "Assets/Settings/XR/Settings/Mock_HMD_Build_Settings.asset"}, + {"290a6e6411d135049940bec2237b8938", "Assets/Settings/XR/Settings/Oculus_Settings.asset"}, + {"4c1640683c539c14080cfd43fbeffbda", "Assets/Settings/XR/XRGeneralSettings.asset"}, + {"93b439a37f63240aca3dd4e01d978a9f", "Assets/UniversalRenderPipelineGlobalSettings.asset"}, + {"38b35347542e5af4c9b140950c5b18db", "Assets/UniversalRenderPipelineGlobalSettings.asset"} + }; + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs.meta new file mode 100644 index 0000000..804f84b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Test Methods/UnityPackage/CheckProjectTemplateAssets.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f02d52a702c712e4e8089f7c2e65bae7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI.meta new file mode 100644 index 0000000..f401e9e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0eed33a351c3c544ba6bf3cd29d24c26 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data.meta new file mode 100644 index 0000000..c3a170c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 461bfd99d0923cd4a8dae2f440af1064 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions.meta new file mode 100644 index 0000000..93d4155 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d7d9c6cc805e072429392e7a378d2c9c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs new file mode 100644 index 0000000..45345a5 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs @@ -0,0 +1,15 @@ +using AssetStoreTools.Validator.Data; +using System; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal interface IValidatorResults + { + event Action OnResultsChanged; + event Action OnRequireSerialize; + + void LoadResult(ValidationResult result); + IEnumerable GetSortedTestGroups(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs.meta new file mode 100644 index 0000000..c914281 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorResults.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91c62333b36d5ef47989289e8f90c056 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs new file mode 100644 index 0000000..cffed7d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs @@ -0,0 +1,31 @@ +using AssetStoreTools.Validator.Data; +using System; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal interface IValidatorSettings + { + event Action OnCategoryChanged; + event Action OnValidationTypeChanged; + event Action OnValidationPathsChanged; + event Action OnRequireSerialize; + + void LoadSettings(ValidationSettings settings); + + string GetActiveCategory(); + void SetActiveCategory(string category); + List GetAvailableCategories(); + + ValidationType GetValidationType(); + void SetValidationType(ValidationType validationType); + + List GetValidationPaths(); + void AddValidationPath(string path); + void RemoveValidationPath(string path); + void ClearValidationPaths(); + bool IsValidationPathValid(string path, out string error); + + IValidator CreateValidator(); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs.meta new file mode 100644 index 0000000..d907440 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc6516196465ac6469258ef8950da607 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs new file mode 100644 index 0000000..761b33b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs @@ -0,0 +1,15 @@ +using AssetStoreTools.Validator.Data; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal interface IValidatorTest + { + int Id { get; } + string Name { get; } + string Description { get; } + ValidationType ValidationType { get; } + TestResult Result { get; } + + void SetResult(TestResult result); + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs.meta new file mode 100644 index 0000000..f1cbf25 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1e4d9ba8de8cfc4aa42786fbbc5037a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs new file mode 100644 index 0000000..325231d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs @@ -0,0 +1,12 @@ +using AssetStoreTools.Validator.Data; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal interface IValidatorTestGroup + { + string Name { get; } + TestResultStatus Status { get; } + IEnumerable Tests { get; } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs.meta new file mode 100644 index 0000000..86c6b56 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Abstractions/IValidatorTestGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa8735b7eb65d3147ab8bdbf922f36cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization.meta new file mode 100644 index 0000000..4122afe --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ec536685238584f41bd268edaaf0ad7d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs new file mode 100644 index 0000000..dc20b6c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace AssetStoreTools.Validator.UI.Data.Serialization +{ + internal class ValidatorStateData + { + [JsonProperty("validation_settings")] + private ValidatorStateSettings _settings; + [JsonProperty("validation_results")] + private ValidatorStateResults _results; + + public ValidatorStateData() + { + _settings = new ValidatorStateSettings(); + _results = new ValidatorStateResults(); + } + + public ValidatorStateSettings GetSettings() + { + return _settings; + } + + public ValidatorStateResults GetResults() + { + return _results; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs.meta new file mode 100644 index 0000000..eea46eb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: da7a885e302cb6b43855b68f44f2c0fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs new file mode 100644 index 0000000..61034e5 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json.Serialization; + +namespace AssetStoreTools.Validator.UI.Data.Serialization +{ + internal class ValidatorStateDataContractResolver : DefaultContractResolver + { + private static ValidatorStateDataContractResolver _instance; + public static ValidatorStateDataContractResolver Instance => _instance ?? (_instance = new ValidatorStateDataContractResolver()); + + private NamingStrategy _namingStrategy; + + private ValidatorStateDataContractResolver() + { + _namingStrategy = new SnakeCaseNamingStrategy(); + } + + protected override string ResolvePropertyName(string propertyName) + { + var resolvedName = _namingStrategy.GetPropertyName(propertyName, false); + if (resolvedName.StartsWith("_")) + return resolvedName.Substring(1); + + return resolvedName; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs.meta new file mode 100644 index 0000000..4187dca --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateDataContractResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60f0e8d9b2ab86547a288c337fb2be0a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs new file mode 100644 index 0000000..ac7d552 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs @@ -0,0 +1,83 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.UI.Data.Serialization +{ + internal class ValidatorStateResults + { + // Primary data + [JsonProperty("validation_status")] + private ValidationStatus _status; + [JsonProperty("test_results")] + private SortedDictionary _results; + + // Secondary data + [JsonProperty("project_path")] + private string _projectPath; + [JsonProperty("had_compilation_errors")] + private bool _hadCompilationErrors; + + public ValidatorStateResults() + { + _projectPath = string.Empty; + _status = ValidationStatus.NotRun; + _hadCompilationErrors = false; + _results = new SortedDictionary(); + } + + public ValidationStatus GetStatus() + { + return _status; + } + + public void SetStatus(ValidationStatus status) + { + if (_status == status) + return; + + _status = status; + } + + public SortedDictionary GetResults() + { + return _results; + } + + public void SetResults(IEnumerable tests) + { + _results.Clear(); + foreach (var test in tests) + { + _results.Add(test.Id, test.Result); + } + } + + public string GetProjectPath() + { + return _projectPath; + } + + public void SetProjectPath(string projectPath) + { + if (_projectPath == projectPath) + return; + + _projectPath = projectPath; + } + + public bool GetHadCompilationErrors() + { + return _hadCompilationErrors; + } + + public void SetHadCompilationErrors(bool hadCompilationErrors) + { + if (_hadCompilationErrors == hadCompilationErrors) + return; + + _hadCompilationErrors = hadCompilationErrors; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs.meta new file mode 100644 index 0000000..876eadb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateResults.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a90c3acfa50e8da4aa3da2b9c669502d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs new file mode 100644 index 0000000..8b5c178 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs @@ -0,0 +1,63 @@ +using AssetStoreTools.Validator.Data; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace AssetStoreTools.Validator.UI.Data.Serialization +{ + internal class ValidatorStateSettings + { + [JsonProperty("category")] + private string _category; + [JsonProperty("validation_type")] + private ValidationType _validationType; + [JsonProperty("validation_paths")] + private List _validationPaths; + + public ValidatorStateSettings() + { + _category = string.Empty; + _validationType = ValidationType.UnityPackage; + _validationPaths = new List(); + } + + public string GetCategory() + { + return _category; + } + + public void SetCategory(string category) + { + if (_category == category) + return; + + _category = category; + } + + public ValidationType GetValidationType() + { + return _validationType; + } + + public void SetValidationType(ValidationType validationType) + { + if (validationType == _validationType) + return; + + _validationType = validationType; + } + + public List GetValidationPaths() + { + return _validationPaths; + } + + public void SetValidationPaths(List validationPaths) + { + if (_validationPaths.SequenceEqual(validationPaths)) + return; + + _validationPaths = validationPaths; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs.meta new file mode 100644 index 0000000..0780a53 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/Serialization/ValidatorStateSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d39d56313ade8a8409aafe95dc84f79a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs new file mode 100644 index 0000000..86650ff --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs @@ -0,0 +1,137 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; +using AssetStoreTools.Validator.UI.Data.Serialization; +using AssetStoreTools.Validator.Utility; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal class ValidatorResults : IValidatorResults + { + private ValidatorStateResults _stateData; + + private IValidatorSettings _settings; + private IEnumerable _tests; + + private readonly TestResultStatus[] _priorityGroups = new TestResultStatus[] + { + TestResultStatus.Undefined, + TestResultStatus.Fail, + TestResultStatus.Warning + }; + + public event Action OnResultsChanged; + public event Action OnRequireSerialize; + + public ValidatorResults(IValidatorSettings settings, ValidatorStateResults stateData) + { + _settings = settings; + _stateData = stateData; + + _tests = GetAllTests(); + + Deserialize(); + } + + private IEnumerable GetAllTests() + { + var tests = new List(); + var testObjects = ValidatorUtility.GetAutomatedTestCases(ValidatorUtility.SortType.Alphabetical); + + foreach (var testObject in testObjects) + { + var testSource = new AutomatedTest(testObject); + var test = new ValidatorTest(testSource); + tests.Add(test); + } + + return tests; + } + + public void LoadResult(ValidationResult result) + { + if (result == null) + return; + + foreach (var test in _tests) + { + if (!result.Tests.Any(x => x.Id == test.Id)) + continue; + + var matchingResult = result.Tests.First(x => x.Id == test.Id); + test.SetResult(matchingResult.Result); + } + + OnResultsChanged?.Invoke(); + + Serialize(result); + } + + public IEnumerable GetSortedTestGroups() + { + var groups = new List(); + var testsByStatus = _tests + .Where(x => x.ValidationType == ValidationType.Generic || x.ValidationType == _settings.GetValidationType()) + .GroupBy(x => x.Result.Status).ToDictionary(x => x.Key, x => x.ToList()); + + foreach (var kvp in testsByStatus) + { + var group = new ValidatorTestGroup(kvp.Key, kvp.Value); + groups.Add(group); + } + + return SortGroups(groups); + } + + private IEnumerable SortGroups(IEnumerable unsortedGroups) + { + var sortedGroups = new List(); + var groups = unsortedGroups.OrderBy(x => x.Status).ToList(); + + // Select priority groups first + foreach (var priority in _priorityGroups) + { + var priorityGroup = groups.FirstOrDefault(x => x.Status == priority); + if (priorityGroup == null) + continue; + + sortedGroups.Add(priorityGroup); + groups.Remove(priorityGroup); + } + + // Add the rest + sortedGroups.AddRange(groups); + + return sortedGroups; + } + + private void Serialize(ValidationResult result) + { + _stateData.SetStatus(result.Status); + _stateData.SetResults(result.Tests); + _stateData.SetProjectPath(result.ProjectPath); + _stateData.SetHadCompilationErrors(result.HadCompilationErrors); + OnRequireSerialize?.Invoke(); + } + + private void Deserialize() + { + if (_stateData == null) + return; + + var serializedResults = _stateData.GetResults(); + foreach (var test in _tests) + { + if (!serializedResults.Any(x => x.Key == test.Id)) + continue; + + var matchingResult = serializedResults.First(x => x.Key == test.Id); + test.SetResult(matchingResult.Value); + } + + OnResultsChanged?.Invoke(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs.meta new file mode 100644 index 0000000..069816b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorResults.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf2839f8b2340294aae39c2965039d2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs new file mode 100644 index 0000000..01abf73 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs @@ -0,0 +1,236 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.UI.Data.Serialization; +using AssetStoreTools.Validator.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal class ValidatorSettings : IValidatorSettings + { + private ValidatorStateSettings _stateData; + + private string _category; + private ValidationType _validationType; + private List _validationPaths; + + public event Action OnCategoryChanged; + public event Action OnValidationTypeChanged; + public event Action OnValidationPathsChanged; + public event Action OnRequireSerialize; + + public ValidatorSettings(ValidatorStateSettings stateData) + { + _stateData = stateData; + + _category = string.Empty; + _validationType = ValidationType.UnityPackage; + _validationPaths = new List(); + + Deserialize(); + } + + public void LoadSettings(ValidationSettings settings) + { + if (settings == null) + return; + + var currentProjectValidationSettings = settings as CurrentProjectValidationSettings; + if (currentProjectValidationSettings == null) + throw new ArgumentException($"Only {nameof(CurrentProjectValidationSettings)} can be loaded"); + + _category = currentProjectValidationSettings.Category; + OnCategoryChanged?.Invoke(); + + _validationType = currentProjectValidationSettings.ValidationType; + OnValidationTypeChanged?.Invoke(); + + _validationPaths = currentProjectValidationSettings.ValidationPaths.ToList(); + OnValidationPathsChanged?.Invoke(); + + Serialize(); + } + + public string GetActiveCategory() + { + return _category; + } + + public void SetActiveCategory(string category) + { + if (category == _category) + return; + + _category = category; + Serialize(); + OnCategoryChanged?.Invoke(); + } + + public List GetAvailableCategories() + { + var categories = new HashSet(); + + var testData = ValidatorUtility.GetAutomatedTestCases(); + foreach (var test in testData) + { + if (test.CategoryInfo == null) + continue; + + foreach (var filter in test.CategoryInfo.Filter) + categories.Add(ConvertSlashToUnicodeSlash(filter)); + } + + return categories.OrderBy(x => x).ToList(); + } + + private string ConvertSlashToUnicodeSlash(string text) + { + return text.Replace('/', '\u2215'); + } + + public ValidationType GetValidationType() + { + return _validationType; + } + + public void SetValidationType(ValidationType validationType) + { + if (validationType == _validationType) + return; + + _validationType = validationType; + + Serialize(); + OnValidationTypeChanged?.Invoke(); + } + + public List GetValidationPaths() + { + return _validationPaths; + } + + public void AddValidationPath(string path) + { + if (string.IsNullOrEmpty(path)) + return; + + if (_validationPaths.Contains(path)) + return; + + // Prevent redundancy for new paths + var existingPath = _validationPaths.FirstOrDefault(x => path.StartsWith(x + "/")); + if (existingPath != null) + { + Debug.LogWarning($"Path '{path}' is already included with existing path: '{existingPath}'"); + return; + } + + // Prevent redundancy for already added paths + var redundantPaths = _validationPaths.Where(x => x.StartsWith(path + "/")).ToArray(); + foreach (var redundantPath in redundantPaths) + { + Debug.LogWarning($"Existing validation path '{redundantPath}' has been made redundant by the inclusion of new validation path: '{path}'"); + _validationPaths.Remove(redundantPath); + } + + _validationPaths.Add(path); + + Serialize(); + OnValidationPathsChanged?.Invoke(); + } + + public void RemoveValidationPath(string path) + { + if (!_validationPaths.Contains(path)) + return; + + _validationPaths.Remove(path); + + Serialize(); + OnValidationPathsChanged?.Invoke(); + } + + public void ClearValidationPaths() + { + if (_validationPaths.Count == 0) + return; + + _validationPaths.Clear(); + + Serialize(); + OnValidationPathsChanged?.Invoke(); + } + + public bool IsValidationPathValid(string path, out string error) + { + error = string.Empty; + + if (string.IsNullOrEmpty(path)) + { + error = "Path cannot be empty"; + return false; + } + + var isAssetsPath = path.StartsWith("Assets/") + || path.Equals("Assets"); + var isPackagePath = PackageUtility.GetPackageByManifestPath($"{path}/package.json", out _); + + if (!isAssetsPath && !isPackagePath) + { + error = "Selected path must be within the Assets folder or point to a root path of a package"; + return false; + } + + if (!Directory.Exists(path)) + { + error = "Path does not exist"; + return false; + } + + if (path.Split('/').Any(x => x.StartsWith(".") || x.EndsWith("~"))) + { + error = $"Path '{path}' cannot be validated as it is a hidden folder and not part of the Asset Database"; + return false; + } + + return true; + } + + public IValidator CreateValidator() + { + var settings = new CurrentProjectValidationSettings() + { + Category = _category, + ValidationType = _validationType, + ValidationPaths = _validationPaths + }; + + var validator = new CurrentProjectValidator(settings); + return validator; + } + + private void Serialize() + { + _stateData.SetCategory(_category); + _stateData.SetValidationType(_validationType); + _stateData.SetValidationPaths(_validationPaths); + + OnRequireSerialize?.Invoke(); + } + + private void Deserialize() + { + if (_stateData == null) + return; + + _category = _stateData.GetCategory(); + _validationType = _stateData.GetValidationType(); + foreach (var path in _stateData.GetValidationPaths()) + _validationPaths.Add(path); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs.meta new file mode 100644 index 0000000..4905909 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 89504c2259614a743a164c5c162a197a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs new file mode 100644 index 0000000..968d5ca --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs @@ -0,0 +1,28 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.TestDefinitions; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal class ValidatorTest : IValidatorTest + { + public int Id { get; private set; } + public string Name { get; private set; } + public string Description { get; private set; } + public ValidationType ValidationType { get; private set; } + public TestResult Result { get; private set; } + + public ValidatorTest(AutomatedTest source) + { + Id = source.Id; + Name = source.Title; + Description = source.Description; + ValidationType = source.ValidationType; + Result = source.Result; + } + + public void SetResult(TestResult result) + { + Result = result; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs.meta new file mode 100644 index 0000000..eb0b837 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 838e8d45ce997d8489185bc194dfcf25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs new file mode 100644 index 0000000..f76dc9f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs @@ -0,0 +1,18 @@ +using AssetStoreTools.Validator.Data; +using System.Collections.Generic; + +namespace AssetStoreTools.Validator.UI.Data +{ + internal class ValidatorTestGroup : IValidatorTestGroup + { + public string Name => Status.ToString(); + public TestResultStatus Status { get; private set; } + public IEnumerable Tests { get; private set; } + + public ValidatorTestGroup(TestResultStatus status, IEnumerable tests) + { + Status = status; + Tests = tests; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs.meta new file mode 100644 index 0000000..46de398 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Data/ValidatorTestGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f5d1fc9ff785904fb2e663e9232a7a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements.meta new file mode 100644 index 0000000..9a9f507 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ee7e5be29b8b184ba2abcd3ed38454e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs new file mode 100644 index 0000000..717e834 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs @@ -0,0 +1,50 @@ +using AssetStoreTools.Validator.UI.Data; +using System; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorButtonElement : VisualElement + { + // Data + private IValidatorSettings _settings; + + // UI + private Button _validateButton; + + public event Action OnValidate; + + public ValidatorButtonElement(IValidatorSettings settings) + { + _settings = settings; + _settings.OnValidationPathsChanged += ValidationPathsChanged; + + Create(); + Deserialize(); + } + + private void Create() + { + _validateButton = new Button(Validate) { text = "Validate" }; + _validateButton.AddToClassList("validator-validate-button"); + + Add(_validateButton); + } + + private void Validate() + { + OnValidate?.Invoke(); + } + + private void ValidationPathsChanged() + { + var validationPathsPresent = _settings.GetValidationPaths().Count > 0; + _validateButton.SetEnabled(validationPathsPresent); + } + + private void Deserialize() + { + ValidationPathsChanged(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs.meta new file mode 100644 index 0000000..f136053 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorButtonElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 44fac105314df6341bf6a70fb3200baf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs new file mode 100644 index 0000000..ab55c2b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs @@ -0,0 +1,114 @@ +using UnityEngine; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorDescriptionElement : VisualElement + { + private const string DescriptionFoldoutText = "Validate your package to ensure your content follows the chosen submission guidelines."; + private const string DescriptionFoldoutContentText = "The validations below do not cover all of the content standards, and passing all validations does not " + + "guarantee that your package will be accepted to the Asset Store.\n\n" + + "The tests are not obligatory for submitting your assets, but they can help avoid instant rejection by the " + + "automated vetting system, or clarify reasons of rejection communicated by the vetting team.\n\n" + + "For more information about the validations, view the message by expanding the tests or contact our support team."; + + private VisualElement _descriptionSimpleContainer; + private Label _descriptionSimpleLabel; + private Button _showMoreButton; + + private VisualElement _descriptionFullContainer; + private Button _showLessButton; + + public ValidatorDescriptionElement() + { + AddToClassList("validator-description"); + Create(); + } + + private void Create() + { + CreateSimpleDescription(); + CreateFullDescription(); + } + + private void CreateSimpleDescription() + { + _descriptionSimpleContainer = new VisualElement(); + _descriptionSimpleContainer.AddToClassList("validator-description-simple-container"); + + _descriptionSimpleLabel = new Label(DescriptionFoldoutText); + _descriptionSimpleLabel.AddToClassList("validator-description-simple-label"); + + _showMoreButton = new Button(ToggleFullDescription) { text = "Show more..." }; + _showMoreButton.AddToClassList("validator-description-show-button"); + _showMoreButton.AddToClassList("validator-description-hyperlink-button"); + + _descriptionSimpleContainer.Add(_descriptionSimpleLabel); + _descriptionSimpleContainer.Add(_showMoreButton); + + Add(_descriptionSimpleContainer); + } + + private void CreateFullDescription() + { + _descriptionFullContainer = new VisualElement(); + _descriptionFullContainer.AddToClassList("validator-description-full-container"); + + var validatorDescription = new Label() + { + text = DescriptionFoldoutContentText + }; + validatorDescription.AddToClassList("validator-description-full-label"); + + var submissionGuidelinesButton = new Button(OpenSubmissionGuidelinesUrl) + { + text = "Submission Guidelines" + }; + submissionGuidelinesButton.AddToClassList("validator-description-hyperlink-button"); + + var supportTicketButton = new Button(OpenSupportTicketUrl) + { + text = "Contact our Support Team" + }; + supportTicketButton.AddToClassList("validator-description-hyperlink-button"); + + _showLessButton = new Button(ToggleFullDescription) { text = "Show less..." }; + _showLessButton.AddToClassList("validator-description-hide-button"); + _showLessButton.AddToClassList("validator-description-hyperlink-button"); + + _descriptionFullContainer.Add(validatorDescription); + _descriptionFullContainer.Add(submissionGuidelinesButton); + _descriptionFullContainer.Add(supportTicketButton); + _descriptionFullContainer.Add(_showLessButton); + + _descriptionFullContainer.style.display = DisplayStyle.None; + Add(_descriptionFullContainer); + } + + private void ToggleFullDescription() + { + var displayFullDescription = _descriptionFullContainer.style.display == DisplayStyle.None; + + if (displayFullDescription) + { + _showMoreButton.style.display = DisplayStyle.None; + _descriptionFullContainer.style.display = DisplayStyle.Flex; + } + else + { + _showMoreButton.style.display = DisplayStyle.Flex; + _descriptionFullContainer.style.display = DisplayStyle.None; + } + } + + private void OpenSubmissionGuidelinesUrl() + { + Application.OpenURL(Constants.Validator.SubmissionGuidelinesUrl); + } + + private void OpenSupportTicketUrl() + { + Application.OpenURL(Constants.Validator.SupportTicketUrl); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs.meta new file mode 100644 index 0000000..898b2ce --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorDescriptionElement.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9866d77420d947ba852055eed2bac895 +timeCreated: 1653383883 \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs new file mode 100644 index 0000000..3a2a170 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs @@ -0,0 +1,128 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.UI.Data; +using UnityEditor; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorPathsElement : VisualElement + { + // Data + private IValidatorSettings _settings; + + // UI + private ScrollView _pathBoxScrollView; + + public ValidatorPathsElement(IValidatorSettings settings) + { + AddToClassList("validator-paths"); + + _settings = settings; + _settings.OnValidationPathsChanged += ValidationPathsChanged; + + Create(); + Deserialize(); + } + + private void Create() + { + var pathSelectionRow = new VisualElement(); + pathSelectionRow.AddToClassList("validator-settings-selection-row"); + + VisualElement labelHelpRow = new VisualElement(); + labelHelpRow.AddToClassList("validator-settings-selection-label-help-row"); + labelHelpRow.style.alignSelf = Align.FlexStart; + + Label pathLabel = new Label { text = "Validation paths" }; + Image pathLabelTooltip = new Image + { + tooltip = "Select the folder (or multiple folders) that your package consists of." + + "\n\nAll files and folders of your package should be contained within " + + "a single root folder that is named after your package " + + "(e.g. 'Assets/[MyPackageName]' or 'Packages/[MyPackageName]')" + + "\n\nIf your package includes special folders that cannot be nested within " + + "the root package folder (e.g. 'WebGLTemplates'), they should be added to this list " + + "together with the root package folder" + }; + + labelHelpRow.Add(pathLabel); + labelHelpRow.Add(pathLabelTooltip); + + var fullPathBox = new VisualElement() { name = "ValidationPaths" }; + fullPathBox.AddToClassList("validator-paths-box"); + + _pathBoxScrollView = new ScrollView { name = "ValidationPathsScrollView" }; + _pathBoxScrollView.AddToClassList("validator-paths-scroll-view"); + + VisualElement scrollViewBottomRow = new VisualElement(); + scrollViewBottomRow.AddToClassList("validator-paths-scroll-view-bottom-row"); + + var addExtraPathsButton = new Button(BrowsePath) { text = "Add a path" }; + addExtraPathsButton.AddToClassList("validator-paths-add-button"); + scrollViewBottomRow.Add(addExtraPathsButton); + + fullPathBox.Add(_pathBoxScrollView); + fullPathBox.Add(scrollViewBottomRow); + + pathSelectionRow.Add(labelHelpRow); + pathSelectionRow.Add(fullPathBox); + + Add(pathSelectionRow); + } + + private VisualElement CreateSinglePathElement(string path) + { + var validationPath = new VisualElement(); + validationPath.AddToClassList("validator-paths-path-row"); + + var folderPathLabel = new Label(path); + folderPathLabel.AddToClassList("validator-paths-path-row-input-field"); + + var removeButton = new Button(() => + { + _settings.RemoveValidationPath(path); + }); + removeButton.text = "X"; + removeButton.AddToClassList("validator-paths-path-row-remove-button"); + + validationPath.Add(folderPathLabel); + validationPath.Add(removeButton); + + return validationPath; + } + + private void BrowsePath() + { + string absolutePath = EditorUtility.OpenFolderPanel("Select a directory", "Assets", ""); + + if (string.IsNullOrEmpty(absolutePath)) + return; + + var relativePath = FileUtility.AbsolutePathToRelativePath(absolutePath, ASToolsPreferences.Instance.EnableSymlinkSupport); + + if (!_settings.IsValidationPathValid(relativePath, out var error)) + { + EditorUtility.DisplayDialog("Invalid path", error, "OK"); + return; + } + + _settings.AddValidationPath(relativePath); + } + + private void ValidationPathsChanged() + { + var validationPaths = _settings.GetValidationPaths(); + + _pathBoxScrollView.Clear(); + foreach (var path in validationPaths) + { + _pathBoxScrollView.Add(CreateSinglePathElement(path)); + } + } + + private void Deserialize() + { + ValidationPathsChanged(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs.meta new file mode 100644 index 0000000..93c9b6b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorPathsElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 370dcd3bc87ace647940b4b07147bf93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs new file mode 100644 index 0000000..a01cad0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs @@ -0,0 +1,47 @@ +using AssetStoreTools.Validator.UI.Data; +using System.Linq; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorResultsElement : ScrollView + { + private IValidatorResults _results; + + public ValidatorResultsElement(IValidatorResults results) + { + AddToClassList("validator-test-list"); + + _results = results; + _results.OnResultsChanged += ResultsChanged; + + Create(); + } + + private void Create() + { + var groups = _results.GetSortedTestGroups().ToList(); + for (int i = 0; i < groups.Count; i++) + { + var groupElement = new ValidatorTestGroupElement(groups[i]); + Add(groupElement); + if (i != groups.Count - 1) + Add(CreateSeparator()); + } + } + + private void ResultsChanged() + { + Clear(); + Create(); + } + + private VisualElement CreateSeparator() + { + var groupSeparator = new VisualElement { name = "GroupSeparator" }; + groupSeparator.AddToClassList("validator-test-list-group-separator"); + + return groupSeparator; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs.meta new file mode 100644 index 0000000..d84970a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorResultsElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 12f80f9088944a149a34b3f078ca859a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs new file mode 100644 index 0000000..6c70e7f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs @@ -0,0 +1,96 @@ +using AssetStoreTools.Validator.UI.Data; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorSettingsElement : VisualElement + { + // Data + private IValidatorSettings _settings; + + // UI + private ToolbarMenu _categoryMenu; + private ValidatorPathsElement _validationPathsElement; + + public ValidatorSettingsElement(IValidatorSettings settings) + { + AddToClassList("validator-settings"); + + _settings = settings; + _settings.OnCategoryChanged += CategoryChanged; + + Create(); + Deserialize(); + } + + public void Create() + { + CreateCategorySelection(); + CreateValidationPathSelection(); + } + + private void CreateCategorySelection() + { + var categorySelectionBox = new VisualElement(); + categorySelectionBox.AddToClassList("validator-settings-selection-row"); + + VisualElement labelHelpRow = new VisualElement(); + labelHelpRow.AddToClassList("validator-settings-selection-label-help-row"); + + Label categoryLabel = new Label { text = "Category" }; + Image categoryLabelTooltip = new Image + { + tooltip = "Choose a base category of your package" + + "\n\nThis can be found in the Publishing Portal when creating the package listing or just " + + "selecting a planned one." + + "\n\nNote: Different categories could have different severities of several test cases." + }; + + labelHelpRow.Add(categoryLabel); + labelHelpRow.Add(categoryLabelTooltip); + + _categoryMenu = new ToolbarMenu { name = "CategoryMenu" }; + _categoryMenu.AddToClassList("validator-settings-selection-dropdown"); + + categorySelectionBox.Add(labelHelpRow); + categorySelectionBox.Add(_categoryMenu); + + // Append available categories + var categories = _settings.GetAvailableCategories(); + foreach (var category in categories) + { + _categoryMenu.menu.AppendAction(category, _ => _settings.SetActiveCategory(category)); + } + + // Append misc category + _categoryMenu.menu.AppendAction("Other", _ => _settings.SetActiveCategory(string.Empty)); + + Add(categorySelectionBox); + } + + private void CreateValidationPathSelection() + { + _validationPathsElement = new ValidatorPathsElement(_settings); + Add(_validationPathsElement); + } + + private void CategoryChanged() + { + var category = _settings.GetActiveCategory(); + if (!string.IsNullOrEmpty(category)) + _categoryMenu.text = category; + else + _categoryMenu.text = "Other"; + } + + private void Deserialize() + { + if (_settings == null) + return; + + // Set initial category + CategoryChanged(); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs.meta new file mode 100644 index 0000000..0522b1b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorSettingsElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 760d25556d755d544bece3a605adea09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs new file mode 100644 index 0000000..a031d2d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs @@ -0,0 +1,239 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.UI.Data; +using AssetStoreTools.Validator.Utility; +using System.Linq; +using UnityEditor.SceneManagement; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.SceneManagement; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorTestElement : VisualElement + { + // Data + private IValidatorTest _test; + private bool _isExpanded; + + // UI + private Button _testFoldoutButton; + private Label _testFoldoutExpandStateLabel; + private Label _testFoldoutLabel; + private Image _testStatusImage; + + private VisualElement _testContent; + private VisualElement _resultMessagesBox; + + public ValidatorTestElement(IValidatorTest test) + { + AddToClassList("validator-test"); + + _test = test; + + Create(); + Unexpand(); + + SubscribeToSceneChanges(); + } + + private void Create() + { + CreateFoldoutButton(); + CreateTestContent(); + CreateTestDescription(); + CreateTestMessages(); + } + + private void CreateFoldoutButton() + { + _testFoldoutButton = new Button(ToggleExpand) { name = _test.Name }; + _testFoldoutButton.AddToClassList("validator-test-foldout"); + + // Expander and Asset Label + VisualElement labelExpanderRow = new VisualElement { name = "labelExpanderRow" }; + labelExpanderRow.AddToClassList("validator-test-expander"); + + _testFoldoutExpandStateLabel = new Label { name = "ExpanderLabel", text = "â–º" }; + _testFoldoutExpandStateLabel.AddToClassList("validator-test-expander-arrow"); + + _testFoldoutLabel = new Label { name = "TestLabel", text = _test.Name }; + _testFoldoutLabel.AddToClassList("validator-text-expander-label"); + + labelExpanderRow.Add(_testFoldoutExpandStateLabel); + labelExpanderRow.Add(_testFoldoutLabel); + + _testStatusImage = new Image + { + name = "TestImage", + image = ValidatorUtility.GetStatusTexture(_test.Result.Status) + }; + + _testStatusImage.AddToClassList("validator-test-expander-image"); + + _testFoldoutButton.Add(labelExpanderRow); + _testFoldoutButton.Add(_testStatusImage); + + Add(_testFoldoutButton); + } + + private void CreateTestContent() + { + _testContent = new VisualElement(); + _testContent.AddToClassList("validator-test-content"); + Add(_testContent); + } + + private void CreateTestDescription() + { + var testCaseDescription = new TextField + { + name = "Description", + value = _test.Description, + isReadOnly = true, + multiline = true, + focusable = false, + doubleClickSelectsWord = false, + tripleClickSelectsLine = false + }; + testCaseDescription.AddToClassList("validator-test-content-textfield"); + +#if UNITY_2022_1_OR_NEWER + testCaseDescription.focusable = true; + testCaseDescription.selectAllOnFocus = false; + testCaseDescription.selectAllOnMouseUp = false; +#endif + + _testContent.Add(testCaseDescription); + } + + private void CreateTestMessages() + { + if (_test.Result.MessageCount == 0) + return; + + _resultMessagesBox = new VisualElement(); + _resultMessagesBox.AddToClassList("validator-test-content-result-messages"); + + switch (_test.Result.Status) + { + case TestResultStatus.Pass: + _resultMessagesBox.AddToClassList("validator-test-content-result-messages-pass"); + break; + case TestResultStatus.Warning: + _resultMessagesBox.AddToClassList("validator-test-content-result-messages-warning"); + break; + case TestResultStatus.Fail: + _resultMessagesBox.AddToClassList("validator-test-content-result-messages-fail"); + break; + } + + for (int i = 0; i < _test.Result.MessageCount; i++) + { + _resultMessagesBox.Add(CreateMessage(_test.Result.GetMessage(i))); + + if (i == _test.Result.MessageCount - 1) + continue; + + var separator = new VisualElement() { name = "Separator" }; + separator.AddToClassList("message-separator"); + _resultMessagesBox.Add(separator); + } + + _testContent.Add(_resultMessagesBox); + } + + private VisualElement CreateMessage(TestResultMessage message) + { + var resultText = message.GetText(); + var clickAction = message.GetClickAction(); + + var resultMessage = new VisualElement { name = "ResultMessageElement" }; + resultMessage.AddToClassList("validator-test-content-result-messages-content"); + + var informationButton = new Button(); + informationButton.AddToClassList("validator-test-content-result-messages-content-button"); + + if (clickAction != null) + { + informationButton.tooltip = clickAction.Tooltip; + informationButton.clicked += clickAction.Execute; + informationButton.SetEnabled(true); + } + + var informationDescription = new Label { name = "InfoDesc", text = resultText }; + informationDescription.AddToClassList("validator-test-content-result-messages-content-label"); + + informationButton.Add(informationDescription); + resultMessage.Add(informationButton); + + for (int i = 0; i < message.MessageObjectCount; i++) + { + var obj = message.GetMessageObject(i); + if (obj == null) + continue; + + if (obj.GetObject() == null) + continue; + + var objectField = new ObjectField() { objectType = obj.GetType(), value = obj.GetObject() }; + objectField.RegisterCallback>((evt) => + objectField.SetValueWithoutNotify(evt.previousValue)); + resultMessage.Add(objectField); + } + + return resultMessage; + } + + private void ToggleExpand() + { + if (!_isExpanded) + Expand(); + else + Unexpand(); + } + + private void Expand() + { + _testFoldoutExpandStateLabel.text = "â–¼"; + _testFoldoutButton.AddToClassList("validator-test-foldout-expanded"); + _testContent.style.display = DisplayStyle.Flex; + _isExpanded = true; + } + + private void Unexpand() + { + _testFoldoutExpandStateLabel.text = "â–º"; + _testFoldoutButton.RemoveFromClassList("validator-test-foldout-expanded"); + _testContent.style.display = DisplayStyle.None; + _isExpanded = false; + } + + private void SubscribeToSceneChanges() + { + // Some result message objects only exist in specific scenes, + // therefore the UI must be refreshed on scene change + var windowToSubscribeTo = Resources.FindObjectsOfTypeAll().FirstOrDefault(); + UnityAction sceneChanged = null; + sceneChanged = new UnityAction((_, __) => RefreshObjects(windowToSubscribeTo)); + EditorSceneManager.activeSceneChangedInEditMode += sceneChanged; + + void RefreshObjects(ValidatorWindow subscribedWindow) + { + // Remove callback if validator window instance changed + var activeWindow = Resources.FindObjectsOfTypeAll().FirstOrDefault(); + if (subscribedWindow == null || subscribedWindow != activeWindow) + { + EditorSceneManager.activeSceneChangedInEditMode -= sceneChanged; + return; + } + + if (_resultMessagesBox != null) + _testContent.Remove(_resultMessagesBox); + + CreateTestMessages(); + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs.meta new file mode 100644 index 0000000..17794ff --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56c93e6f23ba5724da8cc38f832be4e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs new file mode 100644 index 0000000..03bf031 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs @@ -0,0 +1,102 @@ +using AssetStoreTools.Validator.UI.Data; +using AssetStoreTools.Validator.Utility; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Elements +{ + internal class ValidatorTestGroupElement : VisualElement + { + // Data + private IValidatorTestGroup _group; + private bool _isExpanded; + + // UI + private Button _groupFoldoutButton; + private Label _groupExpandStateLabel; + private Label _groupFoldoutLabel; + private Image _groupStatusImage; + + private VisualElement _groupContent; + private List _testElements; + + public ValidatorTestGroupElement(IValidatorTestGroup group) + { + AddToClassList("validator-test-list-group"); + + _group = group; + + Create(); + } + + private void Create() + { + CreateGroupFoldout(); + CreateGroupContent(); + } + + private void CreateGroupFoldout() + { + _groupFoldoutButton = new Button(ToggleExpand); + _groupFoldoutButton.AddToClassList("validator-test-list-group-expander"); + + _groupExpandStateLabel = new Label { name = "ExpanderLabel", text = "â–º" }; + _groupExpandStateLabel.AddToClassList("validator-test-list-group-expander-arrow"); + + _groupStatusImage = new Image + { + name = "TestImage", + image = ValidatorUtility.GetStatusTexture(_group.Status) + }; + _groupStatusImage.AddToClassList("validator-test-list-group-expander-image"); + + _groupFoldoutLabel = new Label() { text = $"{_group.Name} ({_group.Tests.Count()})" }; + _groupFoldoutLabel.AddToClassList("validator-test-list-group-expander-label"); + + _groupFoldoutButton.Add(_groupExpandStateLabel); + _groupFoldoutButton.Add(_groupStatusImage); + _groupFoldoutButton.Add(_groupFoldoutLabel); + + Add(_groupFoldoutButton); + } + + private void CreateGroupContent() + { + _groupContent = new VisualElement(); + _groupContent.AddToClassList("validator-test-list-group-content"); + + Add(_groupContent); + + _testElements = new List(); + foreach (var test in _group.Tests) + { + var testElement = new ValidatorTestElement(test); + _testElements.Add(testElement); + _groupContent.Add(testElement); + } + } + + private void ToggleExpand() + { + if (!_isExpanded) + Expand(); + else + Unexpand(); + } + + private void Expand() + { + _groupExpandStateLabel.text = "â–¼"; + _groupContent.style.display = DisplayStyle.Flex; + _isExpanded = true; + } + + private void Unexpand() + { + _groupExpandStateLabel.text = "â–º"; + _groupContent.style.display = DisplayStyle.None; + _isExpanded = false; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs.meta new file mode 100644 index 0000000..eea4279 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Elements/ValidatorTestGroupElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7c7a8788d0ea324e843a475244d8e18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs new file mode 100644 index 0000000..fafb103 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs @@ -0,0 +1,56 @@ +using AssetStoreTools.Utility; +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services; +using AssetStoreTools.Validator.UI.Views; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI +{ + + internal class ValidatorWindow : AssetStoreToolsWindow + { + protected override string WindowTitle => "Asset Store Validator"; + + private ICachingService _cachingService; + + private ValidatorTestsView _validationTestsView; + + protected override void Init() + { + minSize = new Vector2(350, 350); + + this.SetAntiAliasing(4); + + VisualElement root = rootVisualElement; + + // Clean it out, in case the window gets initialized again + root.Clear(); + + // Getting a reference to the USS Document and adding stylesheet to the root + root.styleSheets.Add(StyleSelector.ValidatorWindow.ValidatorWindowStyle); + root.styleSheets.Add(StyleSelector.ValidatorWindow.ValidatorWindowTheme); + + GetServices(); + ConstructWindow(); + } + + private void GetServices() + { + _cachingService = ValidatorServiceProvider.Instance.GetService(); + } + + private void ConstructWindow() + { + _validationTestsView = new ValidatorTestsView(_cachingService); + rootVisualElement.Add(_validationTestsView); + } + + public void Load(ValidationSettings settings, ValidationResult result) + { + _validationTestsView.LoadSettings(settings); + _validationTestsView.LoadResult(result); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs.meta new file mode 100644 index 0000000..378aa82 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/ValidatorWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a0dc99b826513dd4f868f1cf405c3923 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views.meta new file mode 100644 index 0000000..e90db60 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8a973656ad14b8941b790ed83c874e97 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs new file mode 100644 index 0000000..0b77ff0 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs @@ -0,0 +1,103 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services; +using AssetStoreTools.Validator.UI.Data; +using AssetStoreTools.Validator.UI.Data.Serialization; +using AssetStoreTools.Validator.UI.Elements; +using UnityEditor; +using UnityEngine.UIElements; + +namespace AssetStoreTools.Validator.UI.Views +{ + internal class ValidatorTestsView : VisualElement + { + // Data + private ValidatorStateData _stateData; + private IValidatorSettings _settings; + private IValidatorResults _results; + + private ICachingService _cachingService; + + // UI + private ValidatorSettingsElement _validatorSettingsElement; + private ValidatorButtonElement _validatorButtonElement; + private ValidatorResultsElement _validationTestListElement; + + public ValidatorTestsView(ICachingService cachingService) + { + _cachingService = cachingService; + + if (!_cachingService.GetCachedValidatorStateData(out _stateData)) + _stateData = new ValidatorStateData(); + + _settings = new ValidatorSettings(_stateData.GetSettings()); + _settings.OnRequireSerialize += Serialize; + + _results = new ValidatorResults(_settings, _stateData.GetResults()); + _results.OnRequireSerialize += Serialize; + + Create(); + } + + private void Create() + { + CreateValidatorDescription(); + CreateValidationSettings(); + CreateValidationButton(); + CreateValidatorResults(); + } + + private void CreateValidatorDescription() + { + var validationInfoElement = new ValidatorDescriptionElement(); + Add(validationInfoElement); + } + + private void CreateValidationSettings() + { + _validatorSettingsElement = new ValidatorSettingsElement(_settings); + Add(_validatorSettingsElement); + } + + private void CreateValidationButton() + { + _validatorButtonElement = new ValidatorButtonElement(_settings); + _validatorButtonElement.OnValidate += Validate; + Add(_validatorButtonElement); + } + + private void CreateValidatorResults() + { + _validationTestListElement = new ValidatorResultsElement(_results); + Add(_validationTestListElement); + } + + private void Validate() + { + var validator = _settings.CreateValidator(); + var result = validator.Validate(); + + if (result.Status == ValidationStatus.Failed) + { + EditorUtility.DisplayDialog("Validation failed", result.Exception.Message, "OK"); + return; + } + + LoadResult(result); + } + + public void LoadSettings(ValidationSettings settings) + { + _settings.LoadSettings(settings); + } + + public void LoadResult(ValidationResult result) + { + _results.LoadResult(result); + } + + private void Serialize() + { + _cachingService.CacheValidatorStateData(_stateData); + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs.meta new file mode 100644 index 0000000..90c2cdd --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/UI/Views/ValidatorTestsView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5e0da39c6638684c9d3faf8e62c60d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility.meta new file mode 100644 index 0000000..148dca9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3bc3a78a4b494e44b75268ad1444ab81 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs new file mode 100644 index 0000000..18546ab --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs @@ -0,0 +1,142 @@ +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services; +using AssetStoreTools.Validator.TestDefinitions; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using static AssetStoreTools.Constants; +using ValidatorConstants = AssetStoreTools.Constants.Validator; + +namespace AssetStoreTools.Validator.Utility +{ + internal static class ValidatorUtility + { + public enum SortType + { + Id, + Alphabetical + } + + public static ValidationTestScriptableObject[] GetAutomatedTestCases() => GetAutomatedTestCases(SortType.Id); + + public static ValidationTestScriptableObject[] GetAutomatedTestCases(SortType sortType) + { + string[] guids = AssetDatabase.FindAssets("t:AutomatedTestScriptableObject", new[] { ValidatorConstants.Tests.TestDefinitionsPath }); + ValidationTestScriptableObject[] tests = new ValidationTestScriptableObject[guids.Length]; + for (int i = 0; i < tests.Length; i++) + { + string testPath = AssetDatabase.GUIDToAssetPath(guids[i]); + AutomatedTestScriptableObject test = AssetDatabase.LoadAssetAtPath(testPath); + + tests[i] = test; + } + + switch (sortType) + { + default: + case SortType.Id: + tests = tests.Where(x => x != null).OrderBy(x => x.Id).ToArray(); + break; + case SortType.Alphabetical: + tests = tests.Where(x => x != null).OrderBy(x => x.Title).ToArray(); + break; + } + + return tests; + } + + public static MonoScript GenerateTestScript(string testName, ValidationType validationType) + { + var derivedType = nameof(ITestScript); + var configType = string.Empty; + var scriptPath = string.Empty; + switch (validationType) + { + case ValidationType.Generic: + configType = nameof(GenericTestConfig); + scriptPath = ValidatorConstants.Tests.GenericTestMethodsPath; + break; + case ValidationType.UnityPackage: + configType = nameof(GenericTestConfig); + scriptPath = ValidatorConstants.Tests.UnityPackageTestMethodsPath; + break; + default: + throw new System.Exception("Undefined validation type"); + } + + var newScriptPath = $"{scriptPath}/{testName}"; + if (!newScriptPath.EndsWith(".cs")) + newScriptPath += ".cs"; + + var existingScript = AssetDatabase.LoadAssetAtPath(newScriptPath); + if (existingScript != null) + return existingScript; + + var scriptContent = + $"using AssetStoreTools.Validator.Data;\n" + + $"using AssetStoreTools.Validator.TestDefinitions;\n\n" + + $"namespace AssetStoreTools.Validator.TestMethods\n" + + $"{{\n" + + $" internal class {testName} : {derivedType}\n" + + $" {{\n" + + $" private {configType} _config;\n\n" + + $" // Constructor also accepts dependency injection of registered {nameof(IValidatorService)} types\n" + + $" public {testName}({configType} config)\n" + + $" {{\n" + + $" _config = config;\n" + + $" }}\n\n" + + $" public {nameof(TestResult)} {nameof(ITestScript.Run)}()\n" + + $" {{\n" + + $" var result = new {nameof(TestResult)}() {{ {nameof(TestResult.Status)} = {nameof(TestResultStatus)}.{nameof(TestResultStatus.Undefined)} }};\n" + + $" return result;\n" + + $" }}\n" + + $" }}\n" + + $"}}\n"; + + File.WriteAllText(newScriptPath, scriptContent); + AssetDatabase.Refresh(); + return AssetDatabase.LoadAssetAtPath(newScriptPath); + } + + public static string GetLongestProjectPath() + { + var longPaths = GetProjectPaths(new string[] { "Assets", "Packages" }); + return longPaths.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur); + } + + public static IEnumerable GetProjectPaths(string[] rootPaths) + { + var longPaths = new List(); + var guids = AssetDatabase.FindAssets("*", rootPaths); + + foreach (var guid in guids) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + longPaths.Add(path); + } + + return longPaths; + } + + public static Texture GetStatusTexture(TestResultStatus status) + { + var iconTheme = ""; + if (!EditorGUIUtility.isProSkin) + iconTheme = "_d"; + + switch (status) + { + case TestResultStatus.Pass: + return (Texture)EditorGUIUtility.Load($"{WindowStyles.ValidatorIconsPath}/success{iconTheme}.png"); + case TestResultStatus.Warning: + return (Texture)EditorGUIUtility.Load($"{WindowStyles.ValidatorIconsPath}/warning{iconTheme}.png"); + case TestResultStatus.Fail: + return (Texture)EditorGUIUtility.Load($"{WindowStyles.ValidatorIconsPath}/error{iconTheme}.png"); + default: + return (Texture)EditorGUIUtility.Load($"{WindowStyles.ValidatorIconsPath}/undefined{iconTheme}.png"); + } + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs.meta new file mode 100644 index 0000000..b89dc0e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/Utility/ValidatorUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24792af98b4d87746a4b945e2a45dc2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs new file mode 100644 index 0000000..afdc986 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs @@ -0,0 +1,108 @@ +using AssetStoreTools.Validator.Categories; +using AssetStoreTools.Validator.Data; +using AssetStoreTools.Validator.Services; +using AssetStoreTools.Validator.TestDefinitions; +using AssetStoreTools.Validator.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace AssetStoreTools.Validator +{ + internal abstract class ValidatorBase : IValidator + { + public ValidationSettings Settings { get; private set; } + + private CategoryEvaluator _categoryEvaluator; + private List _automatedTests; + + protected ICachingService CachingService; + + public ValidatorBase(ValidationSettings settings) + { + Settings = settings; + _categoryEvaluator = new CategoryEvaluator(settings?.Category); + + CachingService = ValidatorServiceProvider.Instance.GetService(); + + CreateAutomatedTestCases(); + } + + private void CreateAutomatedTestCases() + { + var testData = ValidatorUtility.GetAutomatedTestCases(ValidatorUtility.SortType.Alphabetical); + _automatedTests = new List(); + + foreach (var t in testData) + { + var test = new AutomatedTest(t); + _automatedTests.Add(test); + } + } + + protected abstract void ValidateSettings(); + protected abstract ValidationResult GenerateValidationResult(); + + public ValidationResult Validate() + { + try + { + ValidateSettings(); + } + catch (Exception e) + { + return new ValidationResult() { Status = ValidationStatus.Failed, Exception = e }; + } + + var result = GenerateValidationResult(); + return result; + } + + protected List GetApplicableTests(params ValidationType[] validationTypes) + { + return _automatedTests.Where(x => validationTypes.Any(y => y == x.ValidationType)).ToList(); + } + + protected ValidationResult RunTests(List tests, ITestConfig config) + { + var completedTests = new List(); + + for (int i = 0; i < tests.Count; i++) + { + var test = tests[i]; + + EditorUtility.DisplayProgressBar("Validating", $"Running validation: {i + 1} - {test.Title}", (float)i / _automatedTests.Count); + + test.Run(config); + + // Adjust result based on categories + var updatedStatus = _categoryEvaluator.Evaluate(test); + test.Result.Status = updatedStatus; + + // Add the result + completedTests.Add(test); + +#if AB_BUILDER + EditorUtility.UnloadUnusedAssetsImmediate(); +#endif + } + + EditorUtility.UnloadUnusedAssetsImmediate(); + EditorUtility.ClearProgressBar(); + + var projectPath = Application.dataPath.Substring(0, Application.dataPath.Length - "/Assets".Length); + var hasCompilationErrors = EditorUtility.scriptCompilationFailed; + var result = new ValidationResult() + { + Status = ValidationStatus.RanToCompletion, + Tests = completedTests, + ProjectPath = projectPath, + HadCompilationErrors = hasCompilationErrors + }; + + return result; + } + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs.meta new file mode 100644 index 0000000..c7fbc8e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Scripts/ValidatorBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2360246050affaa458413c6569c1f925 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles.meta new file mode 100644 index 0000000..a5cbef6 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21f473cb130d5f0458b2823b3a67f789 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss new file mode 100644 index 0000000..b4a4022 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss @@ -0,0 +1,337 @@ +/* Validator Description */ + +.validator-description { + flex-direction: column; + flex-shrink: 0; + + margin: 10px 5px 2px 5px; + padding: 2px 4px; +} + +.validator-description-simple-container { + flex-direction: column; + flex-wrap: wrap; +} + +.validator-description-simple-label { + white-space: normal; +} + +.validator-description-full-container { + margin-top: 12px; +} + +.validator-description-full-label { + white-space: normal; + margin-bottom: 10px; +} + +.validator-description-hyperlink-button { + margin: 0; + padding: 0; + + align-self: flex-start; + cursor: link; +} + +.validator-description-show-button { + margin-top: 12px; +} + +.validator-description-hide-button { + margin-top: 12px; +} + +/* Validator Settings */ + +.validator-settings { + flex-direction: column; + flex-shrink: 0; + + margin: 0px 5px 2px 5px; + padding: 2px 4px; +} + +.validator-settings-selection-row { + flex-direction: row; + flex-grow: 1; + + margin-top: 10px; + padding: 0 3px 0 2px; +} + +.validator-settings-selection-label-help-row { + flex-direction: row; + flex-shrink: 1; + flex-grow: 0; + + align-self: center; + align-items: center; + justify-content: flex-start; + + width: 120px; +} + +.validator-settings-selection-label-help-row > Label { + -unity-font-style: bold; +} + +.validator-settings-selection-label-help-row > Image { + height: 16px; + width: 16px; +} + +.validator-settings-selection-dropdown { + flex-grow: 1; + flex-shrink: 1; + + align-self: stretch; + + margin-right: 0; + margin-left: 3px; + padding: 1px 4px; +} + +/* Validate Button */ + +.validator-validate-button { + align-self: stretch; + + height: 25px; + margin-left: 2px; +} + +/* Validation Paths */ + +.validator-paths { + flex-direction: column; + flex-grow: 1; + flex-shrink: 0; + + margin-bottom: 10px; + padding: 0; +} + +.validator-paths-box { + flex-grow: 1; + flex-direction: column; +} + +.validator-paths-scroll-view { + flex-grow: 1; + height: 100px; + margin-left: 3px; +} + +.validator-paths-scroll-view > .unity-scroll-view__content-viewport +{ + margin-left: 1px; +} + +.validator-paths-scroll-view > * > .unity-scroll-view__content-container +{ + padding: 0 0 0 0; +} + +.validator-paths-scroll-view > * > .unity-scroll-view__vertical-scroller +{ + margin: -1px 0; +} + +.validator-paths-scroll-view-bottom-row { + flex-direction: row-reverse; + margin: -1px 0 0 3px; +} + +.validator-paths-add-button { + margin: 3px 0 0 0; + align-self: center; +} + +.validator-paths-path-row { + flex-direction: row; + flex-grow: 1; + + margin-top: 2px; + padding: 0 5px 0 2px; +} + +.validator-paths-path-row-input-field { + flex-grow: 1; + flex-shrink: 1; + + padding-left: 5px; + + white-space: normal; + -unity-text-align: middle-left; +} + +.validator-paths-path-row-remove-button { + width: 20px; + height: 20px; + margin-left: 2px; + margin-right: 1px; + padding: 1px 0 0 0; +} + +/* Tests List & Groups */ + +.validator-test-list { + flex-grow: 1; + flex-shrink: 1; +} + +.validator-test-list-group-separator { + height: 2px; + margin: 5px 15px; +} + +.validator-test-list-group { + overflow: hidden; +} + +.validator-test-list-group-expander { + flex-direction: row; + flex-grow: 0; + flex-shrink: 0; + + align-items: center; + + min-width: 200px; + min-height: 30px; + + margin: 10px -1px 2px -1px; +} + +.validator-test-list-group-expander-arrow { + align-self: center; + + width: 30px; + height: 30px; + + margin: 0; + padding: 0; +} + +.validator-test-list-group-expander-image { + flex-shrink: 0; + flex-grow: 0; + + width: 17px; + height: 17px; + + margin: 0 7px 0 2px; +} + +.validator-test-list-group-expander-label { + font-size: 14px; +} + +.validator-test-list-group-content { + margin: -2px -2px -2px -2px; +} + +/* Validation Test */ + +.validator-test { + flex-direction: column; + flex-shrink: 0; + flex-grow: 0; + + padding: 2px 0; +} + +.validator-test-foldout { + flex-direction: row; + flex-grow: 1; + flex-shrink: 1; + + align-items: center; + justify-content: space-between; + + min-width: 200px; + min-height: 35px; + + margin: 0; + padding: 5px 10px; +} + +.validator-test-expander { + flex-direction: row; + flex-grow: 1; +} + +.validator-test-expander-arrow { + font-size: 11px; + align-self: center; + + width: 30px; + height: 30px; + + margin: 0; + padding: 0; +} + +.validator-text-expander-label { + flex-grow: 1; + flex-shrink: 1; + + -unity-text-align: middle-left; + -unity-font-style: bold; + white-space: normal; +} + +.validator-test-expander-image { + flex-shrink: 0; + + width: 14px; + height: 14px; + + margin: 0 10px; +} + +.validator-test-content { + flex-grow: 1; + flex-shrink: 0; + + margin: 0; + padding: 5px 30px; +} + +.validator-test-content-textfield { + white-space: normal; +} + +.validator-test-content-result-messages { + flex-direction: column; + flex-shrink: 0; + flex-grow: 0; + + margin: 10px 0 5px 0; + padding: 0 3px 3px 3px; +} + +.validator-test-content-result-messages-content { + flex-basis: auto; + flex-direction: column; + + margin-top: 3px; +} + +.validator-test-content-result-messages-content-button { + align-self: stretch; + + -unity-font-style: normal; + -unity-text-align: middle-left; + + margin: 0; +} + +.validator-test-content-result-messages-content-label { + white-space: normal; +} + +.validator-test-content-result-messages-separator { + height: 3px; + margin: 5px -3px 0 -3px; +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss.meta new file mode 100644 index 0000000..f933a2f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/Style.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c67a10c292c653428af654599fc15aa +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss new file mode 100644 index 0000000..79441e9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss @@ -0,0 +1,166 @@ +.primary-colors +{ + /* Light - lighter */ + background-color: rgb(220, 220, 220); + /* Light - middle */ + background-color: rgb(200, 200, 200); + /* Light - darker */ + background-color: rgb(180, 180, 180); + + /* Dark - lighter */ + background-color: rgb(78, 78, 78); + /* Dark - middle */ + background-color: rgb(68, 68, 68); + /* Dark - darker */ + background-color: rgb(58, 58, 58); + + /* Border color - light */ + border-color: rgb(200, 200, 200); + /* Border color - dark */ + border-color: rgb(33, 33, 33); +} + +/* Validator Description */ + +.validator-description-hyperlink-button { + color: rgb(68, 113, 229); + border-width: 0; + background-color: rgba(0, 0, 0, 0); +} + +.validator-description-hyperlink-button:hover { + color: rgb(68, 133, 229); +} + +.validator-description-hyperlink-button:active { + color: rgb(68, 93, 229); +} + +/* Validator Settings */ + +.validator-settings-selection-label-help-row > Image { + --unity-image: resource("d__Help@2x"); +} + +.validator-settings-selection-dropdown { + color: rgb(238, 238, 238); + background-color: rgb(88, 88, 88); + + border-width: 1px; + border-radius: 3px; + border-color: rgb(36, 36, 36); +} + +/* Validation Paths */ + +.validator-paths-scroll-view { + border-width: 1px; + border-color: rgb(33, 33, 33); + background-color: rgb(58, 58, 58); +} + +.validator-paths-scroll-view > * > .unity-scroll-view__vertical-scroller { + border-right-width: 0; +} + +.validator-paths-path-row-input-field:hover { + background-color: rgb(78, 78, 78); +} + +/* Tests List & Groups */ + +.validator-test-list { + flex-grow: 1; + flex-shrink: 1; +} + +.validator-test-list-group-separator { + background-color: rgb(104, 104, 104); +} + +.validator-test-list-group-expander { + border-width: 0; + border-color: rgba(0, 0, 0, 0); + + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-list-group-expander-arrow { + color: rgb(104, 104, 104); +} + +.validator-test-list-group-expander-label { + color: rgb(255, 255, 255); + -unity-font-style: bold; +} + +/* Validation Test */ + +.validator-test-foldout { + border-width: 0; + border-radius: 0; + background-color: rgb(56, 56, 56); +} + +.validator-test-foldout:hover { + background-color: rgb(68, 68, 68); +} + +.validator-test-foldout:active { + background-color: rgb(48, 48, 48); +} + +.validator-test-foldout-expanded { + background-color: rgb(68, 68, 68); +} + +.validator-test-expander-arrow { + color: rgb(104, 104, 104); +} + +.validator-test-content { + background-color: rgb(68, 68, 68); +} + +.validator-test-content-textfield > .unity-base-field__input { + border-width: 0; + border-color: rgba(0, 0, 0, 0); + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages { + border-left-width: 2px; + border-color: rgb(33, 33, 33); + background-color: rgb(58, 58, 58); +} + +.validator-test-content-result-messages-pass { + border-color: rgb(40, 200, 40); +} + +.validator-test-content-result-messages-warning { + border-color: rgb(200, 140, 40); +} + +.validator-test-content-result-messages-fail { + border-color: rgb(200, 40, 40); +} + +.validator-test-content-result-messages-content-button { + border-width: 0; + border-radius: 0; + + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages-content-button:hover { + background-color: rgb(78, 78, 78); +} + +.validator-test-content-result-messages-content-button:active { + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages-separator { + background-color: rgb(68, 68, 68); +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss.meta new file mode 100644 index 0000000..fc5c649 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeDark.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d09164f0be2befd40aac764571737ff7 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss new file mode 100644 index 0000000..e2c05b4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss @@ -0,0 +1,166 @@ +.primary-colors +{ + /* Light - lighter */ + background-color: rgb(220, 220, 220); + /* Light - middle */ + background-color: rgb(200, 200, 200); + /* Light - darker */ + background-color: rgb(180, 180, 180); + + /* Dark - lighter */ + background-color: rgb(50, 50, 50); + /* Dark - middle */ + background-color: rgb(28, 28, 28); + /* Dark - darker */ + background-color: rgb(0, 0, 0); + + /* Border color - light */ + border-color: rgb(200, 200, 200); + /* Border color - dark */ + border-color: rgb(33, 33, 33); +} + +/* Validator Description */ + +.validator-description-hyperlink-button { + color: rgb(68, 113, 229); + border-width: 0; + background-color: rgba(0, 0, 0, 0); +} + +.validator-description-hyperlink-button:hover { + color: rgb(68, 133, 229); +} + +.validator-description-hyperlink-button:active { + color: rgb(68, 93, 229); +} + +/* Validator Settings */ + +.validator-settings-selection-label-help-row > Image { + --unity-image: resource("_Help@2x"); +} + +.validator-settings-selection-dropdown { + color: rgb(9, 9, 9); + background-color: rgb(228, 228, 228); + + border-width: 1px; + border-radius: 3px; + border-color: rgb(178, 178, 178); +} + +/* Validation Paths */ + +.validator-paths-scroll-view { + border-width: 1px; + border-color: rgb(33, 33, 33); + background-color: rgb(180, 180, 180); +} + +.validator-paths-scroll-view > * > .unity-scroll-view__vertical-scroller { + border-right-width: 0; +} + +.validator-paths-path-row-input-field:hover { + background-color: rgb(200, 200, 200); +} + +/* Tests List & Groups */ + +.validator-test-list { + flex-grow: 1; + flex-shrink: 1; +} + +.validator-test-list-group-separator { + background-color: rgb(77, 77, 77); +} + +.validator-test-list-group-expander { + border-width: 0; + border-color: rgba(0, 0, 0, 0); + + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-list-group-expander-arrow { + color: rgb(77, 77, 77); +} + +.validator-test-list-group-expander-label { + color: rgb(48, 48, 48); + -unity-font-style: bold; +} + +/* Validation Test */ + +.validator-test-foldout { + border-width: 0; + border-radius: 0; + background-color: rgb(198, 198, 198); +} + +.validator-test-foldout:hover { + background-color: rgb(212, 212, 212); +} + +.validator-test-foldout:active { + background-color: rgb(180, 180, 180); +} + +.validator-test-foldout-expanded { + background-color: rgb(212, 212, 212); +} + +.validator-test-expander-arrow { + color: rgb(77, 77, 77); +} + +.validator-test-content { + background-color: rgb(212, 212, 212); +} + +.validator-test-content-textfield > .unity-base-field__input { + border-width: 0; + border-color: rgba(0, 0, 0, 0); + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages { + border-left-width: 2px; + border-color: rgb(33, 33, 33); + background-color: rgb(198, 198, 198); +} + +.validator-test-content-result-messages-pass { + border-color: rgb(40, 200, 40); +} + +.validator-test-content-result-messages-warning { + border-color: rgb(200, 140, 40); +} + +.validator-test-content-result-messages-fail { + border-color: rgb(200, 40, 40); +} + +.validator-test-content-result-messages-content-button { + border-width: 0; + border-radius: 0; + + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages-content-button:hover { + background-color: rgb(212, 212, 212); +} + +.validator-test-content-result-messages-content-button:active { + background-color: rgba(0, 0, 0, 0); +} + +.validator-test-content-result-messages-separator { + background-color: rgb(212, 212, 212); +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss.meta new file mode 100644 index 0000000..c3b2570 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Styles/ThemeLight.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7404a65e6f9592846a20fd5190b12b1a +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests.meta new file mode 100644 index 0000000..4515443 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 82d68ee644bbbb44183019f731e9f205 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic.meta new file mode 100644 index 0000000..e10e15e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 38036e7f211469848b7cf706e3a1febf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Animation Clips.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Animation Clips.asset.meta new file mode 100644 index 0000000..e647903 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Animation Clips.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e0426dd01b5136a4ca1d42d312e12fad +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Audio Clipping.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Audio Clipping.asset.meta new file mode 100644 index 0000000..f7b5dcf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Audio Clipping.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 03c6cd398931b3e41b0784e8589e153f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Colliders.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Colliders.asset.meta new file mode 100644 index 0000000..9b1b8d4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Colliders.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 28ab5af444cf3c849800ed0d8f4a3102 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Compressed Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Compressed Files.asset.meta new file mode 100644 index 0000000..dbf1164 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Compressed Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 53189e6e51235b14192c4d5b3145dd27 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Empty Prefabs.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Empty Prefabs.asset.meta new file mode 100644 index 0000000..d2f3da2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Empty Prefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 08790ea0ed0fd274fb1df75ccc32d415 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check File Menu Names.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check File Menu Names.asset.meta new file mode 100644 index 0000000..a5a922a --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check File Menu Names.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eaf232919893db04b8e05e91f6815424 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check LODs.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check LODs.asset.meta new file mode 100644 index 0000000..deb5c41 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check LODs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ad52ffa05767e9d4bb4d92093ad68b03 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Line Endings.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Line Endings.asset.meta new file mode 100644 index 0000000..699185f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Line Endings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1e7b5480c1d8bda43ab4fa945939e243 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Mesh Prefabs.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Mesh Prefabs.asset.meta new file mode 100644 index 0000000..cff122c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Mesh Prefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 03b362b67028eb443b7ba8b84aedd5f2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Assets.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Assets.asset.meta new file mode 100644 index 0000000..6ba4103 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Assets.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1a3d0b3827fc16347867bee335e8f4ea +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Scenes.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Scenes.asset.meta new file mode 100644 index 0000000..42d6127 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Missing Components in Scenes.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc2cb4e6635aa334ea4a52e2e3ce57c8 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Import Logs.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Import Logs.asset.meta new file mode 100644 index 0000000..ba55c59 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Import Logs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c889cdd91c2f41941a14363dad7a1a38 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Orientation.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Orientation.asset.meta new file mode 100644 index 0000000..4f49313 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Orientation.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 45b2b11da67e8864aacc62d928524b4c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Types.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Types.asset.meta new file mode 100644 index 0000000..c4aef07 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Model Types.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ffef800a102b0e04cae1a3b98549ef1b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Normal Map Textures.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Normal Map Textures.asset.meta new file mode 100644 index 0000000..66ff0da --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Normal Map Textures.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 241ad0174fcadb64da867011d196acbb +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Package Naming.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Package Naming.asset.meta new file mode 100644 index 0000000..f5e0fea --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Package Naming.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04098aa074d151b4a908dfa79dfddec3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Particle Systems.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Particle Systems.asset.meta new file mode 100644 index 0000000..5404fd4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Particle Systems.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 87da7eaed3cee0d4b8ada0b500e3a958 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Path Lengths.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Path Lengths.asset.meta new file mode 100644 index 0000000..4ebd5ac --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Path Lengths.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 21f8ec0602ffac045b1f4a93f8a9b555 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Prefab Transforms.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Prefab Transforms.asset.meta new file mode 100644 index 0000000..713d908 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Prefab Transforms.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 700026f446833f649a3c63b33a90a295 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Script Compilation.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Script Compilation.asset.meta new file mode 100644 index 0000000..3a026fb --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Script Compilation.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 339e21c955642a04289482aa923e10b6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Shader Compilation.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Shader Compilation.asset.meta new file mode 100644 index 0000000..c9ebccf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Shader Compilation.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1450037453608204a989ff95dca62fae +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Texture Dimensions.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Texture Dimensions.asset.meta new file mode 100644 index 0000000..d0318d4 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Texture Dimensions.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c23253393b8e28846b8e02aeaee7e152 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Type Namespaces.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Type Namespaces.asset.meta new file mode 100644 index 0000000..2aa250d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Check Type Namespaces.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd110ee16e8de4d48a602349ed7a0b25 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Executable Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Executable Files.asset.meta new file mode 100644 index 0000000..b27033d --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Executable Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e996c53186de96e49a742d414648a809 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JPG Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JPG Files.asset.meta new file mode 100644 index 0000000..45000c9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JPG Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 781021ae3aa6570468e08d78e3195127 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JavaScript Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JavaScript Files.asset.meta new file mode 100644 index 0000000..d41b9e6 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove JavaScript Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bf01c18b66907f54c99517f6a877e3e0 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Lossy Audio Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Lossy Audio Files.asset.meta new file mode 100644 index 0000000..2026425 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Lossy Audio Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a48657926de5cfb47ac559a7108d03ee +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Mixamo Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Mixamo Files.asset.meta new file mode 100644 index 0000000..84abdb1 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Mixamo Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a0a44055f786ec64f86a07a214d5f831 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove SpeedTree Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove SpeedTree Files.asset.meta new file mode 100644 index 0000000..ffc10af --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove SpeedTree Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 305bbe67f7c644d18bc8a5b2273aa6a4 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Video Files.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Video Files.asset.meta new file mode 100644 index 0000000..e62946c --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/Generic/Remove Video Files.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 893a0df188c2026438be48eed39b301f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage.meta new file mode 100644 index 0000000..0a50cef --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e978e836f2fb224fa11de94e913da49 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Demo Scenes.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Demo Scenes.asset.meta new file mode 100644 index 0000000..d58914f --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Demo Scenes.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f108107be07f69045813d69eff580078 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Documentation.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Documentation.asset.meta new file mode 100644 index 0000000..4ee6335 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Documentation.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b03433f7977b29e4ca7e8d76393a6c26 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Package Size.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Package Size.asset.meta new file mode 100644 index 0000000..63433dd --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Package Size.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 25721b2d7384e5b4f936cf3b33b80a02 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Project Template Assets.asset.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Project Template Assets.asset.meta new file mode 100644 index 0000000..a00554e --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/Editor/Validator/Tests/UnityPackage/Check Project Template Assets.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5392e9de0549574419ff76897d1e0fa1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md new file mode 100644 index 0000000..f3f4c3b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md @@ -0,0 +1,5 @@ +Asset Store Tools v2 copyright © 2025 Unity Technologies + +Source code of the package is licensed under the Unity Companion License (see https://unity.com/legal/licenses/unity-companion-license); otherwise licensed under the Unity Package Distribution License (see https://unity.com/legal/licenses/unity-package-distribution-license ) + +Unless expressly provided otherwise, the software under this license is made available strictly on an “AS IS†BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions. \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md.meta new file mode 100644 index 0000000..b5792bf --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: baeaa62ad0dc664428d6069db8fd986d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json new file mode 100644 index 0000000..abfef6b --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.unity.asset-store-tools", + "displayName": "Asset Store Tools", + "version": "12.0.1", + "unity": "2019.4", + "description": "Whether you're a programmer, game designer, texture artist or 3D modeler, you're welcome to share your creations with everybody in the Unity developer community!", + "type": "tool", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "3.2.1" + } +} \ No newline at end of file diff --git a/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json.meta b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json.meta new file mode 100644 index 0000000..e319cf2 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/com.unity.asset-store-tools/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fca7c22c787fbfd4cb0d7f186668631a +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/AssetStoreUploads/Packages/manifest.json b/TestProjects/AssetStoreUploads/Packages/manifest.json new file mode 100644 index 0000000..47776cc --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/manifest.json @@ -0,0 +1,45 @@ +{ + "dependencies": { + "com.unity.collab-proxy": "2.5.2", + "com.unity.ide.rider": "3.0.31", + "com.unity.ide.visualstudio": "2.0.22", + "com.unity.ide.vscode": "1.2.5", + "com.unity.render-pipelines.universal": "12.1.15", + "com.unity.test-framework": "1.1.33", + "com.unity.textmeshpro": "3.0.6", + "com.unity.timeline": "1.6.5", + "com.unity.ugui": "1.0.0", + "com.unity.visualscripting": "1.9.4", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/TestProjects/AssetStoreUploads/Packages/packages-lock.json b/TestProjects/AssetStoreUploads/Packages/packages-lock.json new file mode 100644 index 0000000..6615bf9 --- /dev/null +++ b/TestProjects/AssetStoreUploads/Packages/packages-lock.json @@ -0,0 +1,417 @@ +{ + "dependencies": { + "com.unity.asset-store-tools": { + "version": "file:com.unity.asset-store-tools", + "depth": 0, + "source": "embedded", + "dependencies": { + "com.unity.nuget.newtonsoft-json": "3.2.1" + } + }, + "com.unity.burst": { + "version": "1.8.18", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.collab-proxy": { + "version": "2.5.2", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ext.nunit": { + "version": "1.0.6", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.ide.rider": { + "version": "3.0.31", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.22", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.test-framework": "1.1.9" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.vscode": { + "version": "1.2.5", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.mathematics": { + "version": "1.2.6", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.nuget.newtonsoft-json": { + "version": "3.2.1", + "depth": 1, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.render-pipelines.core": { + "version": "12.1.15", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.render-pipelines.universal": { + "version": "12.1.15", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.mathematics": "1.2.1", + "com.unity.burst": "1.8.9", + "com.unity.render-pipelines.core": "12.1.15", + "com.unity.shadergraph": "12.1.15" + } + }, + "com.unity.searcher": { + "version": "4.9.1", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.shadergraph": { + "version": "12.1.15", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.render-pipelines.core": "12.1.15", + "com.unity.searcher": "4.9.1" + } + }, + "com.unity.test-framework": { + "version": "1.1.33", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.textmeshpro": { + "version": "3.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.timeline": { + "version": "1.6.5", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ugui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, + "com.unity.visualscripting": { + "version": "1.9.4", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ugui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.modules.ai": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.androidjni": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.animation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.assetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.audio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.cloth": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.director": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.animation": "1.0.0" + } + }, + "com.unity.modules.imageconversion": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.imgui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.jsonserialize": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.particlesystem": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.screencapture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.subsystems": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.terrain": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.terrainphysics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.terrain": "1.0.0" + } + }, + "com.unity.modules.tilemap": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics2d": "1.0.0" + } + }, + "com.unity.modules.ui": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.uielements": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.umbra": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, + "com.unity.modules.unitywebrequest": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.unitywebrequestassetbundle": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestaudio": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.audio": "1.0.0" + } + }, + "com.unity.modules.unitywebrequesttexture": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.unitywebrequestwww": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0" + } + }, + "com.unity.modules.vehicles": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0" + } + }, + "com.unity.modules.video": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" + } + }, + "com.unity.modules.vr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } + }, + "com.unity.modules.wind": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": {} + }, + "com.unity.modules.xr": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.subsystems": "1.0.0" + } + } + } +} diff --git a/TestProjects/AssetStoreUploads/ProjectSettings/BurstAotSettings_StandaloneWindows.json b/TestProjects/AssetStoreUploads/ProjectSettings/BurstAotSettings_StandaloneWindows.json new file mode 100644 index 0000000..e02ae33 --- /dev/null +++ b/TestProjects/AssetStoreUploads/ProjectSettings/BurstAotSettings_StandaloneWindows.json @@ -0,0 +1,17 @@ +{ + "MonoBehaviour": { + "Version": 4, + "EnableBurstCompilation": true, + "EnableOptimisations": true, + "EnableSafetyChecks": false, + "EnableDebugInAllBuilds": false, + "UsePlatformSDKLinker": false, + "CpuMinTargetX32": 0, + "CpuMaxTargetX32": 0, + "CpuMinTargetX64": 0, + "CpuMaxTargetX64": 0, + "CpuTargetsX32": 6, + "CpuTargetsX64": 72, + "OptimizeFor": 0 + } +} diff --git a/TestProjects/AssetStoreUploads/ProjectSettings/CommonBurstAotSettings.json b/TestProjects/AssetStoreUploads/ProjectSettings/CommonBurstAotSettings.json new file mode 100644 index 0000000..0293daf --- /dev/null +++ b/TestProjects/AssetStoreUploads/ProjectSettings/CommonBurstAotSettings.json @@ -0,0 +1,6 @@ +{ + "MonoBehaviour": { + "Version": 4, + "DisabledWarnings": "" + } +} diff --git a/TestProjects/AssetStoreUploads/ProjectSettings/ProjectVersion.txt b/TestProjects/AssetStoreUploads/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..1a62a67 --- /dev/null +++ b/TestProjects/AssetStoreUploads/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 2021.3.45f2 +m_EditorVersionWithRevision: 2021.3.45f2 (88f88f591b2e) diff --git a/TestProjects/AssetStoreUploads/ProjectSettings/SceneTemplateSettings.json b/TestProjects/AssetStoreUploads/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 0000000..6f3e60f --- /dev/null +++ b/TestProjects/AssetStoreUploads/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,167 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicMaterial", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "ignore": false, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/docs/images/building_scene.gif b/docs/images/building_scene.gif new file mode 100644 index 0000000000000000000000000000000000000000..1641e97e6a1f197364bbbf425de3cbd659c3e4b8 GIT binary patch literal 2222269 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℘zxJ9alI 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

j- zr?{$Ml6=dvf5F1mGehAU|9VG4Go@D_nz~OJHU2k8L&fnXwEv7Cfv9KyH8=20$4cy6U}vHN^CFNggQzY z17`dB3UrvIGzxIZ!IKO^A3h}S<=7=unk7&(T~(zy+SDtC;nZuWDzKQ$&8Cu8$N{jT zX9NtrT=y1xCqS$61gJ9~F)#=sNLQa^`#$IO?(Gx}&0*hYTk5{P6MdOp-4JRmC`3h- z3yfq;qS5HtiQt_7J_asH9E5x++(mrGGW-_TqaAdW7&H6j_%xM-fId!D{Z!}RDe&N0 zg61Rc3x2^nyuYThNpzM-W<`yGloEVd6&pILoS1lPaqZ91|EwW0f8{z)(MnV?(<5I+ z;w|L3S$=5gfi5+6FjP^}a;rYlvMiYF%qt(Pi zji^G+1tZrA`n#XR`O+4+y_RmVX*beHVcQknJx7irdU||0y3p+Dd-@IPL$!o3#{^mG zr(8-TbYd!$9&$y41Of{q!a_u8`ygZDXT~aBbcLkc_q_!gAd_H>LjO=>M~>9l5*qlI z7p)rV@+VU~D`$%9z;X6f#y3U9ibgglnnUWKXlj87smVoI4!qfrV-lrwmw!yIXw|NG z%x)yrZZy>{d{s5F2DKu`dV;(5eN{+#5cRim!kg(g<`%Myqk?1ynGXa)1Er%1mL6L1 z`=6~>WUH;hpit9E0bmMO8{Ta!GtpZb_2(fq)gwVViSzG`m=h~**kYcUt<&7j7+V;LT}yYI=gW=%v5 zbD~>n@8}wJ6CpVchFb>IE#+-j5Q}K)gg{HW5MWSX8~zO`MD%3es-#Av>GN{yOHGng zZu)s8C8xAHIU@-Gi4?~q{f06Hj-XZv7f6X9c(O|>Q_SKc({Hl1TG?#)@f_?OcGjpu;sjEktjq|^sv5v&>!TrD zzO^Y#23B7R}8L#2XDvKRtig*+wrv?iCWfr$~wS`#^1 zcWB3!s9{m463%rZN1d=?3t^SoJTo4)X%RDouzru4BvS2^_B~^~Lm& zp6s8B15hSxBk$jB@9 zG0HT`>+3X{x1OCBF1ihEXR@nAL9OE>Av|FZ{8c||G}+DMNZgi5#QMd#Y3-v48vu>? z(*x1aM|{oLtie{Kez^ovGZe9vY0iFyxd^A0#d>Lk<_96fa$d>@HWaZdMnFWvI0by! zxwi658C&9~Au5yTbph_*so8;dEo)9^M|HlMqjVT1@RG%b7MStV9>~vNp<$e`SEX!` z%ErC+UX)`}Y1PHf34T(W=LcI(`gvGZH(rJz20(%^3~z{CtggKvK?YBK70&m8gM4-z zVk@EJc``X!@7Rc{)~=*go|jUG8-M;wvH7cw{D4co(inMaBkoNyFUsbJacuH=;!7ho zL1UquYDCA|V$sowTFS&0U+?~FA8qO8LwvpTnOdfrV)oqCQ#rVw_P~@ z_V9*3Z09RHxeQsbpy}!8r2

mef4X_@~WB_uSY%vdD)Tu&>{b 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