├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── Packages └── com.varneon.package-exporter │ ├── Editor.meta │ ├── Editor │ ├── ExporterWindow.cs │ ├── ExporterWindow.cs.meta │ ├── PackageExportConfiguration.cs │ ├── PackageExportConfiguration.cs.meta │ ├── PackageExportConfigurationExclusions.cs │ ├── PackageExportConfigurationExclusions.cs.meta │ ├── PackageExportConfigurationInclusions.cs │ ├── PackageExportConfigurationInclusions.cs.meta │ ├── PackageExporterConfigurationStorage.cs │ ├── PackageExporterConfigurationStorage.cs.meta │ ├── PackageExporterConfigurationStorageEditor.cs │ ├── PackageExporterConfigurationStorageEditor.cs.meta │ ├── PackageFileNameUtility.cs │ ├── PackageFileNameUtility.cs.meta │ ├── Varneon.PackageExporter.Editor.asmdef │ └── Varneon.PackageExporter.Editor.asmdef.meta │ ├── LICENSE.md │ ├── LICENSE.md.meta │ ├── Resources.meta │ ├── Resources │ ├── AssetExclusionField.uxml │ ├── AssetExclusionField.uxml.meta │ ├── AssetInclusionField.uxml │ ├── AssetInclusionField.uxml.meta │ ├── ConfigurationBlock.uxml │ ├── ConfigurationBlock.uxml.meta │ ├── ConfigurationsWindow.uss │ ├── ConfigurationsWindow.uss.meta │ ├── ConfigurationsWindow.uxml │ ├── ConfigurationsWindow.uxml.meta │ ├── ExporterWindow.uss │ ├── ExporterWindow.uss.meta │ ├── ExporterWindow.uxml │ ├── ExporterWindow.uxml.meta │ ├── FolderExclusionField.uxml │ ├── FolderExclusionField.uxml.meta │ ├── FolderInclusionField.uxml │ ├── FolderInclusionField.uxml.meta │ ├── Icons.meta │ ├── Icons │ │ ├── Icon_Add.png │ │ ├── Icon_Add.png.meta │ │ ├── Icon_Dots.png │ │ ├── Icon_Dots.png.meta │ │ ├── Icon_Gear.png │ │ ├── Icon_Gear.png.meta │ │ ├── Icon_QuestionMark.png │ │ ├── Icon_QuestionMark.png.meta │ │ ├── Icon_Reload.png │ │ ├── Icon_Reload.png.meta │ │ ├── Icon_Remove.png │ │ └── Icon_Remove.png.meta │ ├── NoConfigurationFileMessage.uxml │ ├── NoConfigurationFileMessage.uxml.meta │ ├── NoConfigurationsMessage.uxml │ ├── NoConfigurationsMessage.uxml.meta │ ├── UPMPackageExporter.uss │ ├── UPMPackageExporter.uss.meta │ ├── UPMPackageGenerator.uxml │ └── UPMPackageGenerator.uxml.meta │ ├── Samples~ │ ├── Self-Export Configuration.meta │ └── Self-Export Configuration │ │ ├── PackageExporter_SelfExportConfiguration.asset │ │ └── PackageExporter_SelfExportConfiguration.asset.meta │ ├── package.json │ └── package.json.meta └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: Varneon 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | packageName: "com.varneon.package-exporter" 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: get version 18 | id: version 19 | uses: notiz-dev/github-action-json-property@7c8cf5cc36eb85d8d287a8086a39dac59628eb31 20 | with: 21 | path: "Packages/${{env.packageName}}/package.json" 22 | prop_path: "version" 23 | 24 | - run: echo ${{steps.version.outputs.prop}} 25 | 26 | - name: Set Environment Variables 27 | run: | 28 | echo "zipFile=${{ env.packageName }}-${{ steps.version.outputs.prop }}".zip >> $GITHUB_ENV 29 | echo "unityPackage=${{ env.packageName }}-${{ steps.version.outputs.prop }}.unitypackage" >> $GITHUB_ENV 30 | 31 | - name: Create Zip 32 | uses: thedoctor0/zip-release@09336613be18a8208dfa66bd57efafd9e2685657 33 | with: 34 | type: "zip" 35 | directory: "Packages/${{env.packageName}}/" 36 | filename: "../../${{env.zipFile}}" # make the zip file two directories up, since we start two directories in above 37 | 38 | - run: find "Packages/${{env.packageName}}/" -name \*.meta >> metaList 39 | 40 | - name: Create UnityPackage 41 | uses: pCYSl5EDgo/create-unitypackage@e28c7a4616b2754c564b0a959a03b3c89b756fdb 42 | with: 43 | package-path: ${{ env.unityPackage }} 44 | include-files: metaList 45 | 46 | 47 | - name: Make Release 48 | uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 49 | with: 50 | tag_name: ${{ steps.version.outputs.prop }} 51 | draft: true 52 | files: | 53 | ${{ env.zipFile }} 54 | ${{ env.unityPackage }} 55 | Packages/${{ env.packageName }}/package.json -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/.git 3 | !/.github 4 | !/.gitignore 5 | !/.LICENCE 6 | !/.README.md 7 | !/Packages 8 | /Packages/* 9 | !/Packages/com.varneon.package-exporter 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Varneon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Packages/com.varneon.package-exporter/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ffb0b9a36f0a1714b89ddda44fe7f146 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Packages/com.varneon.package-exporter/Editor/ExporterWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEditor.UIElements; 7 | using UnityEngine; 8 | using UnityEngine.UIElements; 9 | 10 | namespace Varneon.PackageExporter 11 | { 12 | /// 13 | /// The main package exporter window 14 | /// 15 | internal class ExporterWindow : EditorWindow 16 | { 17 | /// 18 | /// UXML assets for the editor window 19 | /// 20 | [SerializeField] 21 | private VisualTreeAsset mainWindowUxml, noConfigurationsFileUxml, noConfigurationsUxml; 22 | 23 | /// 24 | /// All available configuration storages in the project 25 | /// 26 | private PackageExporterConfigurationStorage[] packageExporterConfigurationStorages; 27 | 28 | /// 29 | /// List of all available export configurations in the project 30 | /// 31 | private List packageExportConfigurations; 32 | 33 | /// 34 | /// The active package export configuration 35 | /// 36 | private PackageExportConfiguration activeConfiguration; 37 | 38 | /// 39 | /// Current version of the package for exporting 40 | /// 41 | private string packageVersion; 42 | 43 | /// 44 | /// Current version of the package for exporting 45 | /// 46 | private string PackageVersion 47 | { 48 | set 49 | { 50 | packageVersion = value; 51 | 52 | UpdateOutputFileName(); 53 | 54 | bool dirty = lastPackageVersion != value; 55 | 56 | if (isVersionDirty != dirty) 57 | { 58 | isVersionDirty = dirty; 59 | 60 | lastVersionIndicator.style.display = dirty ? DisplayStyle.Flex : DisplayStyle.None; 61 | } 62 | } 63 | get => packageVersion; 64 | } 65 | 66 | /// 67 | /// Has the version been modified 68 | /// 69 | private bool isVersionDirty; 70 | 71 | /// 72 | /// Last known version of the package 73 | /// 74 | private string lastPackageVersion; 75 | 76 | /// 77 | /// Is the provided version valid 78 | /// 79 | private bool isPackageVersionValid = true; 80 | 81 | /// 82 | /// Is the provided version valid 83 | /// 84 | private bool IsPackageVersionValid 85 | { 86 | set 87 | { 88 | if (isPackageVersionValid != value) 89 | { 90 | isPackageVersionValid = value; 91 | 92 | invalidVersionNotification.style.display = value ? DisplayStyle.None : DisplayStyle.Flex; 93 | } 94 | 95 | exportButton.SetEnabled(value && isFileNameValid); 96 | } 97 | get => isPackageVersionValid; 98 | } 99 | 100 | /// 101 | /// Name of the unitypackage to be exported 102 | /// 103 | private string fileName; 104 | 105 | /// 106 | /// Name of the unitypackage to be exported 107 | /// 108 | private string FileName 109 | { 110 | set 111 | { 112 | isFileNameValid = PackageFileNameUtility.IsNameValid(value); 113 | 114 | invalidFileNameNotification.style.display = isFileNameValid ? DisplayStyle.None : DisplayStyle.Flex; 115 | 116 | exportButton.SetEnabled(isFileNameValid && IsPackageVersionValid); 117 | 118 | fileName = value; 119 | } 120 | get => fileName; 121 | } 122 | 123 | /// 124 | /// Is the current package file name valid 125 | /// 126 | private bool isFileNameValid; 127 | 128 | /// 129 | /// Has the user modified the name 130 | /// 131 | private bool isPackageNameDirty; 132 | 133 | /// 134 | /// Has the user modified the name 135 | /// 136 | private bool IsPackageNameDirty 137 | { 138 | set 139 | { 140 | if (isPackageNameDirty != value) 141 | { 142 | isPackageNameDirty = value; 143 | 144 | resetPackageNameButton.style.display = value ? DisplayStyle.Flex : DisplayStyle.None; 145 | } 146 | } 147 | } 148 | 149 | /// 150 | /// Names for all of the available package configurations 151 | /// 152 | private string[] packageConfigurationNames; 153 | 154 | /// 155 | /// Index of the selected package export configuration 156 | /// 157 | private int activeConfigurationIndex; 158 | 159 | /// 160 | /// Are there no configurations available for exporting packages 161 | /// 162 | private bool noConfigurationsAvailable; 163 | 164 | /// 165 | /// Is there no PackageExporterConfigurations scriptable object available 166 | /// 167 | private bool noConfigurationFileAvailable; 168 | 169 | /// 170 | /// The paths of the asset that will be exported in the package 171 | /// 172 | private HashSet pathsToExport = new HashSet(); 173 | 174 | /// 175 | /// ToolbarMenu for selecting the active configuration 176 | /// 177 | private ToolbarMenu configurationMenu; 178 | 179 | /// 180 | /// The underlying DropdownMenu which contains the configuration menu elements 181 | /// 182 | private DropdownMenu configurationDropdown; 183 | 184 | /// 185 | /// Package tree preview's loading screen 186 | /// 187 | private VisualElement packagePreviewLoadingScreen; 188 | 189 | /// 190 | /// Package tree preview's loading screen's progress bar 191 | /// 192 | private VisualElement packagePreviewProgressBar; 193 | 194 | /// 195 | /// ListView for previewing the package contents 196 | /// 197 | private ListView packagePreview; 198 | 199 | /// 200 | /// TextField for specifying the package version 201 | /// 202 | private TextField packageVersionField; 203 | 204 | /// 205 | /// TextField for previewing and modifying the final package name 206 | /// 207 | private TextField packageNameField; 208 | 209 | /// 210 | /// Notification for indicating that the file name of the package is invalid 211 | /// 212 | private VisualElement invalidFileNameNotification; 213 | 214 | /// 215 | /// Button for resetting the package name to origican naming pattern 216 | /// 217 | private Button resetPackageNameButton; 218 | 219 | /// 220 | /// Root VisualElement for the last package version indicator 221 | /// 222 | private VisualElement lastVersionIndicator; 223 | 224 | /// 225 | /// Underlying Label element for displaying the previous package version 226 | /// 227 | private Label lastVersionLabel; 228 | 229 | /// 230 | /// Root VisualElement for invalid package version notification 231 | /// 232 | private VisualElement invalidVersionNotification; 233 | 234 | /// 235 | /// Button for exporting the package 236 | /// 237 | private Button exportButton; 238 | 239 | /// 240 | /// Is this window currently focused 241 | /// 242 | private bool isWindowFocused = true; 243 | 244 | private PackagePreviewDirectoryWalker packagePreviewDirectoryWalker; 245 | 246 | private struct PackagePreviewDirectoryWalker 247 | { 248 | internal bool Active; 249 | 250 | internal int PathCount; 251 | 252 | internal int CurrentPathIndex; 253 | 254 | internal string CurrentDirectory; 255 | 256 | internal int DirectoryDepth; 257 | 258 | internal Foldout CurrentFoldout; 259 | 260 | internal PackagePreviewDirectoryWalker(int exportedPathCount) 261 | { 262 | Active = true; 263 | 264 | PathCount = exportedPathCount; 265 | 266 | CurrentPathIndex = 0; 267 | 268 | CurrentDirectory = string.Empty; 269 | 270 | DirectoryDepth = 0; 271 | 272 | CurrentFoldout = null; 273 | } 274 | } 275 | 276 | /// 277 | /// Which page the editor window is currently on 278 | /// 279 | private Page currentPage; 280 | 281 | /// 282 | /// Enum for describing different pages of the editor window 283 | /// 284 | private enum Page 285 | { 286 | None, 287 | Main, 288 | NoConfigurationsFile, 289 | NoConfigurations 290 | } 291 | 292 | #region Editor Window Methods 293 | [MenuItem("Varneon/Package Exporter/Exporter")] 294 | private static void OpenWindow() 295 | { 296 | ExporterWindow window = GetWindow(); 297 | window.titleContent.text = "Package Exporter"; 298 | window.minSize = new Vector2(300, 300); 299 | window.Show(); 300 | } 301 | 302 | private void OnEnable() 303 | { 304 | currentPage = Page.None; 305 | 306 | LoadPage(Page.Main); 307 | 308 | LoadPackageConfigurationStorages(); 309 | } 310 | 311 | private void OnDestroy() 312 | { 313 | if (packagePreviewDirectoryWalker.Active) 314 | { 315 | Debug.Log("Still building package preview, aborting..."); 316 | 317 | EditorApplication.update -= IteratePackageTreePreviewBuilder; 318 | } 319 | } 320 | 321 | private void OnLostFocus() 322 | { 323 | isWindowFocused = false; 324 | } 325 | 326 | private void OnFocus() 327 | { 328 | if (isWindowFocused) { return; } 329 | 330 | if (noConfigurationFileAvailable || noConfigurationsAvailable) 331 | { 332 | LoadPackageConfigurationStorages(false); 333 | 334 | return; 335 | } 336 | 337 | if (!packageConfigurationNames.SequenceEqual(GetAvailablePackageConfigurationNames())) 338 | { 339 | activeConfigurationIndex = 0; 340 | 341 | LoadPackageConfigurations(); 342 | } 343 | } 344 | #endregion 345 | 346 | /// 347 | /// Generates the UI for the main page 348 | /// 349 | private void InitializeMainPage() 350 | { 351 | mainWindowUxml.CloneTree(rootVisualElement); 352 | 353 | configurationMenu = rootVisualElement.Q("ConfigurationMenu"); 354 | 355 | configurationMenu.RegisterCallback(a => 356 | { 357 | GenericMenu menu = new GenericMenu(); 358 | 359 | menu.AddItem(new GUIContent("Open Export Directory"), false, () => EditorUtility.RevealInFinder(activeConfiguration.ExportDirectory)); 360 | 361 | menu.ShowAsContext(); 362 | }); 363 | 364 | configurationDropdown = configurationMenu.menu; 365 | 366 | rootVisualElement.Q