From 0dc84d07dabd2de54d6eafc3aa06cb449c6a04ce Mon Sep 17 00:00:00 2001 From: Dalton Lima Date: Tue, 16 Dec 2025 11:47:20 +0000 Subject: [PATCH 1/4] add test json file --- tests/projects-v1.json | 745 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 tests/projects-v1.json diff --git a/tests/projects-v1.json b/tests/projects-v1.json new file mode 100644 index 0000000..db57dd0 --- /dev/null +++ b/tests/projects-v1.json @@ -0,0 +1,745 @@ +{ + "schema_version": "v1", + "data": { + "/Users/testuser/Dev/Project-Alpha": { + "title": "Project-Alpha", + "lastModified": 1760740589089, + "isCustomEditor": false, + "path": "/Users/testuser/Dev/Project-Alpha", + "containingFolderPath": "/Users/testuser/Dev", + "version": "6000.0.58f2", + "architecture": "arm64", + "changeset": "92dee566b325", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "f7e662f8803e0d947aa1d72844c30bbb", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Cosmic-Odyssey": { + "title": "Cosmic-Odyssey", + "lastModified": 1750778637791, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Cosmic-Odyssey", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.38f1", + "architecture": "x86_64", + "changeset": "c5d5a7410213", + "isFavorite": false, + "cloudProjectId": "e9b3fb88-c352-48df-b87a-9b7b700c8403", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Cosmic Odyssey", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Cyber-Runner-2049": { + "title": "Cyber-Runner-2049", + "lastModified": 1725890506469, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Cyber-Runner-2049", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.30f1", + "architecture": "arm64", + "changeset": "70558241b701", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Cyber Runner", + "hasCustomDisplayName": false + }, + "/Users/testuser/Prototypes/Dungeon-Crawler-Kit": { + "title": "Dungeon-Crawler-Kit", + "lastModified": 1724334140836, + "isCustomEditor": false, + "path": "/Users/testuser/Prototypes/Dungeon-Crawler-Kit", + "containingFolderPath": "/Users/testuser/Prototypes", + "version": "2022.3.30f1", + "architecture": "arm64", + "changeset": "70558241b701", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Dungeon Crawler Kit", + "hasCustomDisplayName": false + }, + "/Users/testuser/Work/Project-Phoenix": { + "title": "Project-Phoenix", + "lastModified": 1752850188053, + "isCustomEditor": false, + "path": "/Users/testuser/Work/Project-Phoenix", + "containingFolderPath": "/Users/testuser/Work", + "version": "2022.3.59f1", + "architecture": "arm64", + "changeset": "630718f645a5", + "isFavorite": false, + "cloudProjectId": "48686587-c24b-4026-9ec8-2a7d9ff01d57", + "organizationId": "game-dev-inc", + "localProjectId": "2e4eca68034fc45e3822bc221983e876", + "cloudEnabled": true, + "projectName": "Project Phoenix", + "hasCustomDisplayName": false + }, + "/Users/testuser/Templates/Platformer-Template": { + "title": "Platformer-Template", + "lastModified": 1723740025146, + "isCustomEditor": false, + "path": "/Users/testuser/Templates/Platformer-Template", + "containingFolderPath": "/Users/testuser/Templates", + "version": "2022.3.30f1", + "architecture": "arm64", + "changeset": "70558241b701", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Platformer Template", + "hasCustomDisplayName": false + }, + "/Users/testuser/Dev/Project-Titan": { + "title": "Project-Titan", + "lastModified": 1760745830376, + "isCustomEditor": false, + "path": "/Users/testuser/Dev/Project-Titan", + "containingFolderPath": "/Users/testuser/Dev", + "version": "6000.0.58f2", + "architecture": "arm64", + "changeset": "92dee566b325", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "793dc04879dc50c429de36d670803f0b", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Galaxy-Explorer": { + "title": "Galaxy-Explorer", + "lastModified": 1758710012934, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Galaxy-Explorer", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f1", + "architecture": "x86_64", + "changeset": "4af31df58517", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Galaxy Explorer", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Space-Shooter-X": { + "title": "Space-Shooter-X", + "lastModified": 1756389518789, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Space-Shooter-X", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.59f1", + "architecture": "arm64", + "changeset": "630718f645a5", + "isFavorite": false, + "cloudProjectId": "073df16e-7b09-4df2-b1dc-f64531c1dc47", + "organizationId": "game-dev-inc", + "localProjectId": "d85887b92b0d6418db9fe967c0bcc98c", + "cloudEnabled": true, + "projectName": "Space Shooter X", + "hasCustomDisplayName": false + }, + "/Users/testuser/Work/Project-Valkyrie": { + "title": "Project-Valkyrie", + "lastModified": 1757581559105, + "isCustomEditor": false, + "path": "/Users/testuser/Work/Project-Valkyrie", + "containingFolderPath": "/Users/testuser/Work", + "version": "2022.3.59f1", + "architecture": "arm64", + "changeset": "630718f645a5", + "isFavorite": false, + "cloudProjectId": "48686587-c24b-4026-9ec8-2a7d9ff01d57", + "organizationId": "game-dev-inc", + "localProjectId": "2e4eca68034fc45e3822bc221983e876", + "cloudEnabled": true, + "projectName": "Project Valkyrie", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Puzzle-Mania": { + "title": "Puzzle-Mania", + "lastModified": 1762180899078, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Puzzle-Mania", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": false, + "cloudProjectId": "aed5e964-4fec-42d5-acd9-43eb702cc0cb", + "organizationId": "game-dev-inc", + "localProjectId": "2404850b249d149f096d70b15224e8e2", + "cloudEnabled": true, + "projectName": "Puzzle Mania", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Racing-Pro": { + "title": "Racing-Pro", + "lastModified": 1758818855118, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Racing-Pro", + "containingFolderPath": "/Users/testuser/Games", + "version": "6000.0.57f1", + "architecture": "arm64", + "changeset": "b7b9860b7bbd", + "isFavorite": false, + "cloudProjectId": "9d6a701e-dd3d-4792-a38f-69a21b7be63f", + "organizationId": "game-dev-inc", + "localProjectId": "c376c7b58408a4bd19c058cff0ca85f9", + "cloudEnabled": true, + "projectName": "Racing Pro", + "hasCustomDisplayName": false + }, + "/Users/testuser/Templates/RPG-Template": { + "title": "RPG-Template", + "lastModified": 1723752129646, + "isCustomEditor": false, + "path": "/Users/testuser/Templates/RPG-Template", + "containingFolderPath": "/Users/testuser/Templates", + "version": "2022.3.30f1", + "architecture": "arm64", + "changeset": "70558241b701", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "RPG Template", + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/CI-Test-Project": { + "title": "CI-Test-Project", + "lastModified": 1760706687079, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/CI-Test-Project", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": false, + "cloudProjectId": "f4b819e6-b3c1-4d75-8faf-6b13965a5244", + "organizationId": "game-dev-inc", + "localProjectId": "0594e693079ae469e973f512803eff3d", + "cloudEnabled": true, + "projectName": "CI Test Project", + "hasCustomDisplayName": false + }, + "/Users/testuser/Dev/Gem-Crusher": { + "title": "Gem-Crusher", + "lastModified": 1761497497119, + "isCustomEditor": false, + "path": "/Users/testuser/Dev/Gem-Crusher", + "containingFolderPath": "/Users/testuser/Dev", + "version": "6000.0.58f2", + "architecture": "arm64", + "changeset": "92dee566b325", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "65cf472d372aa2349917beee23eea404", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Starters/Starter-Kit-3D": { + "title": "Starter-Kit-3D", + "lastModified": 1760608047440, + "isCustomEditor": false, + "path": "/Users/testuser/Starters/Starter-Kit-3D", + "containingFolderPath": "/Users/testuser/Starters", + "version": "6000.0.58f2", + "architecture": "arm64", + "changeset": "92dee566b325", + "isFavorite": true, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Starter Kit 3D", + "hasCustomDisplayName": false + }, + "/Users/testuser/Dev/Project-Omega": { + "title": "Project-Omega", + "lastModified": 1760747089839, + "isCustomEditor": false, + "path": "/Users/testuser/Dev/Project-Omega", + "containingFolderPath": "/Users/testuser/Dev", + "version": "6000.0.58f2", + "architecture": "arm64", + "changeset": "92dee566b325", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "ebe44fde83b97364ab90235bf0766771", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/City-Builder": { + "title": "City-Builder", + "lastModified": null, + "isCustomEditor": false, + "path": "/Users/testuser/Games/City-Builder", + "version": "2022.3.49f1", + "architecture": "arm64", + "isFavorite": false + }, + "/Users/testuser/Games/Word-Quest": { + "title": "Word-Quest", + "lastModified": 1762529597841, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Quest", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": true, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Word Quest", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Puzzle-Swap": { + "title": "Puzzle-Swap", + "lastModified": 1738618914167, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Puzzle-Swap", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.38f1", + "architecture": "arm64", + "changeset": "c5d5a7410213", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Puzzle Swap", + "hasCustomDisplayName": false + }, + "/Users/testuser/SDK-Updates/SDK-Update-Test": { + "title": "SDK-Update-Test", + "lastModified": 1737113008864, + "isCustomEditor": false, + "path": "/Users/testuser/SDK-Updates/SDK-Update-Test", + "containingFolderPath": "/Users/testuser/SDK-Updates", + "version": "2022.3.54f1", + "architecture": "arm64", + "changeset": "129125d4e700", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "1da1888e98a0a41a2be29d82a31facf5", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Strategy-Wars": { + "title": "Strategy-Wars", + "lastModified": 1757591889040, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Strategy-Wars", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f1", + "architecture": "arm64", + "changeset": "4af31df58517", + "isFavorite": false, + "cloudProjectId": "3dbbb983-dd5c-4052-9246-8fd3248de15b", + "organizationId": "game-dev-inc", + "localProjectId": "2c2ab20102809410c868429588be7f0d", + "cloudEnabled": true, + "projectName": "Strategy Wars", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Gem-Fall": { + "title": "Gem-Fall", + "lastModified": 1752750720232, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Gem-Fall", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.53f1", + "architecture": "arm64", + "changeset": "df4e529d20d3", + "isFavorite": false, + "cloudProjectId": "331aa4c5-573e-4f7c-aa18-071b334d75d4", + "organizationId": "game-dev-inc", + "localProjectId": "bd573e92e64df4b5d912252803a9a6df", + "cloudEnabled": true, + "projectName": "Gem Fall", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Word-Master": { + "title": "Word-Master", + "lastModified": 1759746750982, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Master", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f1", + "architecture": "arm64", + "changeset": "4af31df58517", + "isFavorite": false, + "cloudProjectId": "4474a490-2891-4776-b1e3-234ccf8a4b56", + "organizationId": "game-dev-inc", + "localProjectId": "a58b94f142973424482ba2f68b17c0fc", + "cloudEnabled": true, + "projectName": "Word Master", + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/API-Test/Product-Catalog-API": { + "title": "Product-Catalog-API", + "lastModified": 1749667865640, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/API-Test/Product-Catalog-API", + "containingFolderPath": "/Users/testuser/Tests/API-Test", + "version": "2022.3.53f1", + "architecture": "arm64", + "changeset": "df4e529d20d3", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "b4edc9c704753ec4baaa970d9a94e9bf", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Package-Dev/Product-Catalog": { + "title": "Product-Catalog", + "lastModified": 1748987961744, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Package-Dev/Product-Catalog", + "containingFolderPath": "/Users/testuser/Tests/Package-Dev", + "version": "2022.3.59f1", + "architecture": "arm64", + "changeset": "630718f645a5", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "012bc062c439e4f278bf1c3b325b3845", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Case-Project": { + "title": "Case-Project", + "lastModified": 1749567875790, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Case-Project", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.53f1", + "architecture": "arm64", + "changeset": "df4e529d20d3", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "b88eb7511e9604ce781891b8564e62dc", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Catalog-API-Test": { + "title": "Catalog-API-Test", + "lastModified": 1749636409384, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Catalog-API-Test", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.56f1", + "architecture": "arm64", + "changeset": "dd0c98481d00", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "90b811a2dd94e4f6a99b4c66da0ec77b", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Catalog-Client": { + "title": "Catalog-Client", + "lastModified": 1751902823420, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Catalog-Client", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.53f1", + "architecture": "arm64", + "changeset": "df4e529d20d3", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "513566cd774e69241996743c2d222c98", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Starters/Word-Search-Starter": { + "title": "Word-Search-Starter", + "lastModified": 1753697939384, + "isCustomEditor": false, + "path": "/Users/testuser/Starters/Word-Search-Starter", + "containingFolderPath": "/Users/testuser/Starters", + "version": "2022.3.53f1", + "architecture": "arm64", + "changeset": "df4e529d20d3", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Word Search Starter", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Word-Blast": { + "title": "Word-Blast", + "lastModified": 1754584335685, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Blast", + "containingFolderPath": "/Users/testuser/Games", + "version": "6000.0.51f1", + "architecture": "arm64", + "changeset": "01c3ff5872c5", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Word Blast", + "hasCustomDisplayName": false + }, + "/Users/testuser/Legacy/Color-World": { + "title": "Color-World", + "lastModified": 1752750831990, + "isCustomEditor": false, + "path": "/Users/testuser/Legacy/Color-World", + "containingFolderPath": "/Users/testuser/Legacy", + "version": "2019.4.19f1", + "architecture": "x86_64", + "changeset": "ca5b14067cec", + "isFavorite": false, + "cloudProjectId": "6083b272-5f06-4175-8ceb-00e3daf9a9ea", + "organizationId": "game-dev-inc", + "localProjectId": "edfd850ffdced6444ba7689179b83486", + "cloudEnabled": true, + "projectName": "Color World", + "hasCustomDisplayName": false + }, + "/Users/testuser/Archive/Old-Playground": { + "title": "Old-Playground", + "lastModified": 1684504695084, + "isCustomEditor": false, + "path": "/Users/testuser/Archive/Old-Playground", + "containingFolderPath": "/Users/testuser/Archive", + "version": "2021.3.25f1", + "architecture": "unknown", + "changeset": "68ef2c4f8861", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "9689a557a38944e558ac00cbeea801d3", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/SDKs/My-SDK": { + "title": "My-SDK", + "lastModified": 1702052629965, + "isCustomEditor": false, + "path": "/Users/testuser/SDKs/My-SDK", + "containingFolderPath": "/Users/testuser/SDKs", + "version": "2022.3.13f1", + "architecture": "unknown", + "changeset": "5f90a5ebde0f", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "87db1f2f6e6034ba4acde6b0c3a54a72", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Garden-Maker": { + "title": "Garden-Maker", + "lastModified": 1701462408582, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Garden-Maker", + "containingFolderPath": "/Users/testuser/Games", + "version": "2021.3.7f1", + "architecture": "unknown", + "changeset": "24e8595d6d43", + "isFavorite": false, + "cloudProjectId": "598659cb-0bd5-492f-b383-9322991a7ad7", + "organizationId": "game-dev-inc", + "localProjectId": "736805e769bc14845bd2527988c59a4c", + "cloudEnabled": true, + "projectName": "Garden Maker", + "hasCustomDisplayName": false + }, + "/Users/testuser/Templates/Magic-Template": { + "title": "Magic-Template", + "lastModified": 1685957767007, + "isCustomEditor": false, + "path": "/Users/testuser/Templates/Magic-Template", + "containingFolderPath": "/Users/testuser/Templates", + "version": "2020.3.24f1", + "architecture": "x86_64", + "changeset": "79c78de19888", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "736805e769bc14845bd2527988c59a4c", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Legacy/Art-Project": { + "title": "Art-Project", + "lastModified": 1730738651599, + "isCustomEditor": false, + "path": "/Users/testuser/Legacy/Art-Project", + "containingFolderPath": "/Users/testuser/Legacy", + "version": "2021.3.27f1", + "architecture": "unknown", + "changeset": "ca3ffb99bcc6", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "26761bfc7b4cd455d81fb5582ab9dd80", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Card-Battler": { + "title": "Card-Battler", + "lastModified": 1713906600071, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Card-Battler", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.4f1", + "architecture": "unknown", + "changeset": "35713cd46cd7", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Card Battler", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Tile-Core-Game": { + "title": "Tile-Core-Game", + "lastModified": 1714748427824, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Tile-Core-Game", + "containingFolderPath": "/Users/testuser/Games", + "version": "2021.3.33f1", + "architecture": "unknown", + "changeset": "ee5a2aa03ab2", + "isFavorite": false, + "cloudProjectId": "4a595938-fb87-4702-84ee-ce6b4c71127b", + "organizationId": "game-dev-inc", + "localProjectId": "6eb073262a096447b9292d45bc59dc3c", + "cloudEnabled": true, + "projectName": "Tile Core Game", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Word-Game-Live": { + "title": "Word-Game-Live", + "lastModified": 1714603873637, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Game-Live", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.4f1", + "architecture": "unknown", + "changeset": "35713cd46cd7", + "isFavorite": false, + "cloudProjectId": "e9b3fb88-c352-48df-b87a-9b7b700c8403", + "organizationId": "game-dev-inc", + "localProjectId": "235c95c32eaca43afb3da8fcdf3bf1bc", + "cloudEnabled": true, + "projectName": "Word Game Live", + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Word-Game-2": { + "title": "Word-Game-2", + "lastModified": 1762180899078, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Game-2", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": false, + "cloudProjectId": "aed5e964-4fec-42d5-acd9-43eb702cc0cb", + "organizationId": "game-dev-inc", + "localProjectId": "2404850b249d149f096d70b15224e8e2", + "cloudEnabled": true, + "projectName": "Word Game 2", + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Core-Isolation-Test": { + "title": "Core-Isolation-Test", + "lastModified": 1714394840710, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Core-Isolation-Test", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.23f1", + "architecture": "arm64", + "changeset": "dbb3f7c5b5c6", + "isFavorite": false, + "cloudProjectId": "aed5e964-4fec-42d5-acd9-43eb702cc0cb", + "organizationId": "game-dev-inc", + "localProjectId": "2404850b249d149f096d70b15224e8e2", + "cloudEnabled": true, + "projectName": "Core Isolation Test", + "hasCustomDisplayName": false + }, + "/Users/testuser/Tests/Blaster-Test": { + "title": "Blaster-Test", + "lastModified": 1759504695866, + "isCustomEditor": false, + "path": "/Users/testuser/Tests/Blaster-Test", + "containingFolderPath": "/Users/testuser/Tests", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": false, + "cloudProjectId": null, + "organizationId": null, + "localProjectId": "586d2910efa89428d954d8bbeecfad84", + "cloudEnabled": true, + "projectName": null, + "hasCustomDisplayName": false + }, + "/Users/testuser/Games/Word-Game-Themes": { + "title": "Word-Game-Themes", + "lastModified": 1760217097543, + "isCustomEditor": false, + "path": "/Users/testuser/Games/Word-Game-Themes", + "containingFolderPath": "/Users/testuser/Games", + "version": "2022.3.62f2", + "architecture": "arm64", + "changeset": "7670c08855a9", + "isFavorite": true, + "cloudProjectId": "bf3846d8-5359-4762-8bc1-25ea5e13cee2", + "organizationId": "game-dev-inc", + "localProjectId": "84d81c2a5f8384498b0b50ef204796c1", + "cloudEnabled": true, + "projectName": "Word Game Themes", + "hasCustomDisplayName": false + } + } +} \ No newline at end of file From 3f66aacaedc0717e1548c9fec62647dbcd29a2c7 Mon Sep 17 00:00:00 2001 From: Dalton Lima Date: Tue, 16 Dec 2025 11:51:33 +0000 Subject: [PATCH 2/4] add AGENTS.md --- AGENTS.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ff7894e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# AGENTS.md + +## Build/Lint/Test Commands + +This is a Python script project with no complex build process. + +- Lint: `ruff check .` or `pylint open-unity.py` +- Test: No automated tests currently exist; manual testing via `python open-unity.py` is recommended. The `tests/projects-v1.json` file is a copy from `Unity Hub` (v`3.15.3`) with anonymized data, preserving the structure for testing. + +## Code Style Guidelines + +- Python code follows PEP8 style guidelines +- Use 4 spaces for indentation +- Import statements should be grouped (standard library, third-party, local) +- Function and variable names use snake_case +- Class names use PascalCase +- Error handling with try/except blocks where appropriate +- Docstrings for all functions +- Type hints should be included for function parameters and return values + +## Special Notes + +- This is a simple Python command-line utility for macOS +- The script is designed to be easily customizable +- Uses pathlib for path operations +- ANSI color codes for error messages in terminal output +- Supports command-line arguments passed to Unity editor From 011aead2d93dda1b00271784df2a11bc792e4779 Mon Sep 17 00:00:00 2001 From: Dalton Lima Date: Tue, 16 Dec 2025 14:39:00 +0000 Subject: [PATCH 3/4] add new behavior --- open-unity.py | 135 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 26 deletions(-) diff --git a/open-unity.py b/open-unity.py index ef77805..9d61a71 100755 --- a/open-unity.py +++ b/open-unity.py @@ -4,60 +4,145 @@ import sys import re import subprocess +import json +from dataclasses import dataclass +from datetime import datetime from pathlib import Path +from typing import Optional + +@dataclass +class UnityProject: + """Represents a Unity project entry from Unity Hub's data.""" + path: Path + title: str + version: str + last_modified: Optional[datetime] + + @staticmethod + def from_json(json_data: dict): + last_modified_timestamp = json_data.get("lastModified") + last_modified = None + if last_modified_timestamp: + # The lastModified timestamp is in milliseconds, so we convert it to seconds. + last_modified = datetime.fromtimestamp(last_modified_timestamp / 1000) + + return UnityProject( + path=Path(json_data["path"]), + title=json_data["title"], + version=json_data["version"], + last_modified=last_modified, + ) + +def get_recent_projects(): + """Load and return recent Unity projects from Unity Hub.""" + unity_hub_projects_file = Path.home() / "Library/Application Support/UnityHub/projects-v1.json" + if not unity_hub_projects_file.is_file(): + return [] + + with open(unity_hub_projects_file, "r") as file: + projects_data = json.load(file) + + projects = [ + UnityProject.from_json(data) + for data in projects_data.get("data", {}).values() + if is_valid_project_data(data) + ] + # Sort projects by last modified date, most recent first. + projects.sort(key=lambda p: p.last_modified, reverse=True) + return projects -def main(): - # Starting from the current working directory, search in multiple locations for the Unity ProjectVersion file. - current_directory = Path.cwd() - project_settings_file = Path('ProjectSettings') / 'ProjectVersion.txt' - project_settings_paths = [ - current_directory / project_settings_file, - current_directory / 'frontend' / project_settings_file, - current_directory / '..' / project_settings_file - ] +def is_valid_project_data(data: dict) -> bool: + """Check if project data contains all required fields.""" + required_fields = ["path", "title", "version", "lastModified"] + return all(field in data and data.get(field) for field in required_fields) - project_settings_path = None +def main(): + # If a path is provided as the first argument, open that project. + # Otherwise, show the recent projects list. + if len(sys.argv) > 1 and (sys.argv[1] == '.' or os.path.isdir(sys.argv[1])): + project_path = Path(sys.argv[1]).resolve() + extra_args = sys.argv[2:] + open_project_from_path(project_path, extra_args) + else: + open_recent_project() + +def open_recent_project(): + recent_projects = get_recent_projects() + if not recent_projects: + exit_with_error("Couldn't find any recent Unity projects.", 1) + + project = show_project_selection(recent_projects) + if project: + launch_unity(project.path, project.version) + +def show_project_selection(projects): + try: + # Use fzf for a better selection UI, if available. + project_strings = [f"{p.title} ({p.path.parent.name}) - {p.version}" for p in projects] + fzf_process = subprocess.run( + ['fzf', '--height', '40%', '--reverse', '--prompt', 'Select a Unity Project> '], + input='\n'.join(project_strings), + capture_output=True, + text=True, + check=True + ) + selected_string = fzf_process.stdout.strip() + if selected_string: + selected_index = project_strings.index(selected_string) + return projects[selected_index] + except (FileNotFoundError, subprocess.CalledProcessError): + # Fallback to a simple numbered list if fzf is not installed or fails. + print("Recent projects:") + for i, project in enumerate(projects[:10]): + print(f" {i + 1}: {project.title} ({project.version})") + try: + selection = int(input("Select a project (1-10): ")) + if 1 <= selection <= len(projects[:10]): + return projects[selection - 1] + except (ValueError, IndexError): + exit_with_error("Invalid selection.", 1) + return None - for path in project_settings_paths: - if path.is_file(): - project_settings_path = path.resolve() - break +def open_project_from_path(project_path: Path, args: list): + # Check for the Unity ProjectVersion file in the specified path. + project_settings_file = project_path / 'ProjectSettings' / 'ProjectVersion.txt' - if not project_settings_path: - paths_string = '\n'.join(str(path.resolve()) for path in project_settings_paths) - exit_with_error(f"Couldn't find ProjectSettings file in:\n{paths_string}", 1) + if not project_settings_file.is_file(): + exit_with_error(f"Couldn't find ProjectSettings/ProjectVersion.txt in:\n{project_path}", 1) - unity_version = find_version(project_settings_path) + unity_version = find_version(project_settings_file) if unity_version is None: exit_with_error("Couldn't find Unity version in ProjectSettings file", 2) + launch_unity(project_path, unity_version, args) + +def launch_unity(project_path: Path, unity_version: str, extra_args: list = None): + if extra_args is None: + extra_args = [] + unity_editor_path = f"/Applications/Unity/Hub/Editor/{unity_version}/Unity.app/Contents/MacOS/Unity" if not os.path.isfile(unity_editor_path): exit_with_error(f"Couldn't find Unity Editor installation at {unity_editor_path}", 3) - # The project path is the one which contains the Assets, Library and ProjectSettings folders. - project_path = str(project_settings_path.parent.parent) - # Invoke the Unity Editor with the found project path and some global arguments. command = [ unity_editor_path, - '-projectPath', project_path, + '-projectPath', str(project_path), '-cacheServerEnableDownload', 'false', '-cacheServerEnableUpload', 'false', ] # Add any custom arguments passed to this script. - command.extend(sys.argv[1:]) + command.extend(extra_args) print(f"Starting Unity {unity_version} with arguments: {' '.join(command[1:])}") # Start a new process which continues to live even after the shell is closed. subprocess.Popen(command) - def find_version(project_settings_path): # Extract the Unity version from the ProjectSettings file. version_pattern = re.compile(r'm_EditorVersion: (.*)') @@ -68,7 +153,6 @@ def find_version(project_settings_path): return match.group(1) return None - def exit_with_error(message: str, code: int): if sys.stderr.isatty(): # Use ANSI colors in terminal output, but not when piping to a file. @@ -78,6 +162,5 @@ def exit_with_error(message: str, code: int): sys.stderr.write(message + '\n') sys.exit(code) - if __name__ == '__main__': main() From 946d694c02763d99405e20352086fa75ec650c24 Mon Sep 17 00:00:00 2001 From: Dalton Lima Date: Tue, 16 Dec 2025 14:40:11 +0000 Subject: [PATCH 4/4] update documentation (add design document) --- README.md | 95 ++++++++++++++++++++++++++++------------------ docs/cli-design.md | 52 +++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 37 deletions(-) create mode 100644 docs/cli-design.md diff --git a/README.md b/README.md index d023f91..5cf1342 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,81 @@ # Unity Command Line Launcher -image - A lightweight command-line script designed to speedup opening Unity projects. With a single command, this script launches a Unity project directly from within the project's directory tree, bypassing the Unity Hub for enhanced speed and convenience. It's tailored for developers who prefer the efficiency of a terminal-based approach. -Recommended workflow: -1) Open your Unity project in your IDE from the recent projects list -2) Use a keyboard shortcut to open the command line window in the open Unity project working directory -3) Use the launcher command to open the Unity project +The script now also supports opening recent projects from Unity Hub, with a fuzzy finder for quick access. -This approach is much quicker than: -1) Waiting for the slow Unity Hub to start -2) Selecting the Unity project in the list and waiting for it to open -3) Double-clicking a script or selecting _Assets > Open C# Project_ in Unity to open the IDE +image -# Support -The script currently only supports macOS and was tested on Ventura 13.4. +## Support -# Setup -- Install Python 3 -- Place the launcher script anywhere on your machine -- Create a globally available command alias that points to the script. For example, for the ZSH shell, add `alias unity="~/bin/open-unity.py"` to the `.zshrc` file. +The script currently only supports macOS. -```bash -echo 'alias unity="~/bin/open-unity.py"' >> ~/.zshrc -``` +## Setup + +1. **Install Python 3:** The script is written in Python. +2. **Install `fzf` (Recommended):** For a better project selection UI, install `fzf`. + + ```bash + brew install fzf + ``` + + `fzf` is a powerful command-line fuzzy finder. Learn more at [github.com/junegunn/fzf](https://github.com/junegunn/fzf). + +3. **Place the script:** Put the `open-unity.py` script in a memorable location, like `~/bin/`. + +4. **Create an alias:** Make the script easily accessible from your terminal by adding an alias to your shell's configuration file (e.g., `.zshrc` or `.bash_profile`). + + ```zsh + # Add this line to your ~/.zshrc + alias unity="~/bin/open-unity.py" + ``` + + Alternatively, you can add it by running this command: + + ```zsh + echo 'alias unity="~/bin/open-unity.py"' >> ~/.zshrc + ``` + + Another option is to rename the script to `unity` and move it to a directory in your `PATH`. + +## Customization + +The script is intended to be used in source form so that it can be easily customized. For example, you can update the directories searched for the Unity project or change the default command-line arguments passed to Unity. -Alternatively, rename the script to _unity_ and place it in a directory that is part of the PATH shell variable. +## Design -# Usage -Open your IDE (e.g. Rider or Visual Studio) with the desired Unity project or any command line session in one of the following directories: -- The Unity project root directory (which contains Assets, Library and ProjectSettings) -- Any direct child directory (e.g. Assets, Library, ProjectSettings) -- A directory which contains the directory "frontend" which is the Unity project root +For more details on the design of the command-line interface, see the [CLI Design Document](docs/cli-design.md). -Invoke the `unity` command alias to start Unity and open the current project: +## Usage -```bash +The script has two main modes of operation: + +### 1. Open Recent Projects (Default) + +When run without any arguments, the script displays a list of your recent Unity projects, fetched from the Unity Hub's data. + +```zsh unity ``` -Pass additional arguments via the command: +This will open an interactive selector. If you have `fzf` (a command-line fuzzy finder) installed, you'll get a powerful search interface. Otherwise, it will present a simple numbered list of the 10 most recent projects. -```bash -unity -force-metal -``` +You can also explicitly trigger this mode with the `--recent` flag: -Find the available Unity editor command line arguments in the [official documentation](https://docs.unity3d.com/Manual/EditorCommandLineArguments.html). +```zsh +unity --recent +``` -# Customization -The script is intended to be used in source form so that it can be easily customized, e.g. update the directories that are searched for the Unity project or change the command line arguments passed to Unity by default. +### 2. Open Project from Current Directory -## Add an alias to .zshrc +To open a Unity project located in the current directory (or a parent directory), pass any argument to the script. This maintains the original behavior of the tool. -You can add an `alias` to `unity` - just add this to your `.zshrc` +A common use case is to pass arguments directly to the Unity Editor: ```zsh -alias unity="~/bin/open-unity.py +unity -force-metal ``` + +This is useful when you are already in a project's directory and want to open it with specific command-line flags. + +Find the available Unity editor command line arguments in the [official documentation](https://docs.unity3d.com/Manual/EditorCommandLineArguments.html). diff --git a/docs/cli-design.md b/docs/cli-design.md new file mode 100644 index 0000000..49d2057 --- /dev/null +++ b/docs/cli-design.md @@ -0,0 +1,52 @@ +# Proposal: New CLI Behavior for `open-unity` + +This document outlines the proposed changes to the command-line interface for the `open-unity` script to incorporate the new feature of opening recent projects. + +## Guiding Principle + +The primary goal is to make the most common workflow—opening a recent project—as fast and intuitive as possible, while retaining the original functionality for opening a project from the current directory. + +## Proposed CLI Design + +The script will operate in one of two modes based on the arguments provided. + +### 1. Default Mode: Open Recent Project (No Arguments) + +When the script is executed without any arguments, it will be the primary and default behavior. + +```bash +open-unity +``` + +**Action:** + +1. The script will read the `projects-v1.json` file from Unity Hub's application support directory. +2. It will parse the list of projects, sort them by the `lastModified` date (most recent first). +3. It will display an interactive selector to the user: + - **If `fzf` is installed:** Use `fzf` to show a fuzzy-findable list of recent projects. This provides the best user experience. + - **If `fzf` is not installed:** Fall back to a simple, numbered list of the top 10 recent projects, prompting the user to enter a number. +4. Once a project is selected, the script will launch the corresponding Unity Editor version with the selected project path. + +### 2. Legacy Mode: Open Project from Current Directory (With Arguments) + +When the script is executed with a valid directory path as its first argument, it will trigger the legacy behavior. This path is mandatory to enter this mode. Any additional arguments will be forwarded to the Unity Editor. + +```bash +# Open project in the current directory +open-unity . + +# Open project in a specific directory +open-unity /path/to/my/project + +# Open project in the current directory with extra arguments for Unity +open-unity . -force-metal -batchmode +``` + +**Action:** + +1. The script will check if the first argument is a valid directory path (e.g., `.` or `/path/to/project`). +2. If it is a valid path, it will look for a `ProjectSettings/ProjectVersion.txt` file directly within that path. The script will no longer search parent or common subdirectories. +3. If a project is found, it will launch the corresponding Unity Editor. +4. All subsequent arguments (`-force-metal`, `-batchmode`, etc.) are forwarded directly to the Unity Editor executable. +5. If no arguments are provided, or if the first argument is not a valid directory path (e.g., it's a flag like `-h`), the script will default to showing the recent projects list. +This design makes opening recent projects the default, most accessible feature, while a simple argument like `.` or any Unity flag cleanly switches to the context-sensitive local project opening.