├── .gitignore
├── appveyor.yml
├── changelog.md
├── img
├── unity_editor_with_nuget.jpg
├── unitynuget.png
└── unitynuget.svg
├── license.txt
├── readme.md
├── registry.json
└── src
├── UnityNuGet.Server
├── ApplicationBuilderExtension.cs
├── Connected Services
│ └── Application Insights
│ │ └── ConnectedService.json
├── Controllers
│ └── ApiController.cs
├── NuGetRedirectLogger.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── RegistryCacheSingleton.cs
├── RegistryCacheUpdater.cs
├── Startup.cs
├── UnityNuGet.Server.csproj
├── app.config
├── appsettings.Development.json
└── appsettings.json
├── UnityNuGet.Tests
├── RegistryCacheTests.cs
└── UnityNuGet.Tests.csproj
├── UnityNuGet.sln
├── UnityNuGet
├── JsonCommonExtensions.cs
├── JsonObjectBase.cs
├── Npm
│ ├── NpmDistribution.cs
│ ├── NpmError.cs
│ ├── NpmObject.cs
│ ├── NpmPackage.cs
│ ├── NpmPackageInfo.cs
│ ├── NpmPackageListAllResponse.cs
│ ├── NpmPackageRegistry.cs
│ ├── NpmPackageVersion.cs
│ └── NpmSourceRepository.cs
├── NuGetConsoleLogger.cs
├── Registry.cs
├── RegistryCache.cs
├── RegistryEntry.cs
├── UnityMeta.cs
├── UnityNuGet.csproj
└── UnityPackage.cs
└── global.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 |
33 | # Visual Studio 2015/2017 cache/options directory
34 | .vs/
35 | # Uncomment if you have tasks that create the project's static files in wwwroot
36 | #wwwroot/
37 |
38 | # Remove idea/jetbrains files and tmp/ folder
39 | .idea/
40 | tmp/
41 |
42 | # Visual Studio 2017 auto generated files
43 | Generated\ Files/
44 |
45 | # MSTest test Results
46 | [Tt]est[Rr]esult*/
47 | [Bb]uild[Ll]og.*
48 |
49 | # NUnit
50 | *.VisualState.xml
51 | TestResult.xml
52 | nunit-*.xml
53 |
54 | # Build Results of an ATL Project
55 | [Dd]ebugPS/
56 | [Rr]eleasePS/
57 | dlldata.c
58 |
59 | # Benchmark Results
60 | BenchmarkDotNet.Artifacts/
61 |
62 | # .NET Core
63 | project.lock.json
64 | project.fragment.lock.json
65 | artifacts/
66 |
67 | # StyleCop
68 | StyleCopReport.xml
69 |
70 | # Files built by Visual Studio
71 | *_i.c
72 | *_p.c
73 | *_h.h
74 | *.ilk
75 | *.meta
76 | *.obj
77 | *.iobj
78 | *.pch
79 | *.pdb
80 | *.ipdb
81 | *.pgc
82 | *.pgd
83 | *.rsp
84 | *.sbr
85 | *.tlb
86 | *.tli
87 | *.tlh
88 | *.tmp
89 | *.tmp_proj
90 | *_wpftmp.csproj
91 | *.log
92 | *.vspscc
93 | *.vssscc
94 | .builds
95 | *.pidb
96 | *.svclog
97 | *.scc
98 |
99 | # Chutzpah Test files
100 | _Chutzpah*
101 |
102 | # Visual C++ cache files
103 | ipch/
104 | *.aps
105 | *.ncb
106 | *.opendb
107 | *.opensdf
108 | *.sdf
109 | *.cachefile
110 | *.VC.db
111 | *.VC.VC.opendb
112 |
113 | # Visual Studio profiler
114 | *.psess
115 | *.vsp
116 | *.vspx
117 | *.sap
118 |
119 | # Visual Studio Trace Files
120 | *.e2e
121 |
122 | # TFS 2012 Local Workspace
123 | $tf/
124 |
125 | # Guidance Automation Toolkit
126 | *.gpState
127 |
128 | # ReSharper is a .NET coding add-in
129 | _ReSharper*/
130 | *.[Rr]e[Ss]harper
131 | *.DotSettings.user
132 |
133 | # JustCode is a .NET coding add-in
134 | .JustCode
135 |
136 | # TeamCity is a build add-in
137 | _TeamCity*
138 |
139 | # DotCover is a Code Coverage Tool
140 | *.dotCover
141 |
142 | # AxoCover is a Code Coverage Tool
143 | .axoCover/*
144 | !.axoCover/settings.json
145 |
146 | # Visual Studio code coverage results
147 | *.coverage
148 | *.coveragexml
149 |
150 | # NCrunch
151 | _NCrunch_*
152 | .*crunch*.local.xml
153 | nCrunchTemp_*
154 |
155 | # MightyMoose
156 | *.mm.*
157 | AutoTest.Net/
158 |
159 | # Web workbench (sass)
160 | .sass-cache/
161 |
162 | # Installshield output folder
163 | [Ee]xpress/
164 |
165 | # DocProject is a documentation generator add-in
166 | DocProject/buildhelp/
167 | DocProject/Help/*.HxT
168 | DocProject/Help/*.HxC
169 | DocProject/Help/*.hhc
170 | DocProject/Help/*.hhk
171 | DocProject/Help/*.hhp
172 | DocProject/Help/Html2
173 | DocProject/Help/html
174 |
175 | # Click-Once directory
176 | publish/
177 |
178 | # Publish Web Output
179 | *.[Pp]ublish.xml
180 | *.azurePubxml
181 | # Note: Comment the next line if you want to checkin your web deploy settings,
182 | # but database connection strings (with potential passwords) will be unencrypted
183 | *.pubxml
184 | *.publishproj
185 |
186 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
187 | # checkin your Azure Web App publish settings, but sensitive information contained
188 | # in these scripts will be unencrypted
189 | PublishScripts/
190 |
191 | # NuGet Packages
192 | *.nupkg
193 | # NuGet Symbol Packages
194 | *.snupkg
195 | # The packages folder can be ignored because of Package Restore
196 | **/[Pp]ackages/*
197 | # except build/, which is used as an MSBuild target.
198 | !**/[Pp]ackages/build/
199 | # Uncomment if necessary however generally it will be regenerated when needed
200 | #!**/[Pp]ackages/repositories.config
201 | # NuGet v3's project.json files produces more ignorable files
202 | *.nuget.props
203 | *.nuget.targets
204 |
205 | # Microsoft Azure Build Output
206 | csx/
207 | *.build.csdef
208 |
209 | # Microsoft Azure Emulator
210 | ecf/
211 | rcf/
212 |
213 | # Windows Store app package directories and files
214 | AppPackages/
215 | BundleArtifacts/
216 | Package.StoreAssociation.xml
217 | _pkginfo.txt
218 | *.appx
219 | *.appxbundle
220 | *.appxupload
221 |
222 | # Visual Studio cache files
223 | # files ending in .cache can be ignored
224 | *.[Cc]ache
225 | # but keep track of directories ending in .cache
226 | !?*.[Cc]ache/
227 |
228 | # Others
229 | ClientBin/
230 | ~$*
231 | *~
232 | *.dbmdl
233 | *.dbproj.schemaview
234 | *.jfm
235 | *.pfx
236 | *.publishsettings
237 | orleans.codegen.cs
238 |
239 | # Including strong name files can present a security risk
240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
241 | #*.snk
242 |
243 | # Since there are multiple workflows, uncomment next line to ignore bower_components
244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
245 | #bower_components/
246 |
247 | # RIA/Silverlight projects
248 | Generated_Code/
249 |
250 | # Backup & report files from converting an old project file
251 | # to a newer Visual Studio version. Backup files are not needed,
252 | # because we have git ;-)
253 | _UpgradeReport_Files/
254 | Backup*/
255 | UpgradeLog*.XML
256 | UpgradeLog*.htm
257 | ServiceFabricBackup/
258 | *.rptproj.bak
259 |
260 | # SQL Server files
261 | *.mdf
262 | *.ldf
263 | *.ndf
264 |
265 | # Business Intelligence projects
266 | *.rdl.data
267 | *.bim.layout
268 | *.bim_*.settings
269 | *.rptproj.rsuser
270 | *- [Bb]ackup.rdl
271 | *- [Bb]ackup ([0-9]).rdl
272 | *- [Bb]ackup ([0-9][0-9]).rdl
273 |
274 | # Microsoft Fakes
275 | FakesAssemblies/
276 |
277 | # GhostDoc plugin setting file
278 | *.GhostDoc.xml
279 |
280 | # Node.js Tools for Visual Studio
281 | .ntvs_analysis.dat
282 | node_modules/
283 |
284 | # Visual Studio 6 build log
285 | *.plg
286 |
287 | # Visual Studio 6 workspace options file
288 | *.opt
289 |
290 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
291 | *.vbw
292 |
293 | # Visual Studio LightSwitch build output
294 | **/*.HTMLClient/GeneratedArtifacts
295 | **/*.DesktopClient/GeneratedArtifacts
296 | **/*.DesktopClient/ModelManifest.xml
297 | **/*.Server/GeneratedArtifacts
298 | **/*.Server/ModelManifest.xml
299 | _Pvt_Extensions
300 |
301 | # Paket dependency manager
302 | .paket/paket.exe
303 | paket-files/
304 |
305 | # FAKE - F# Make
306 | .fake/
307 |
308 | # CodeRush personal settings
309 | .cr/personal
310 |
311 | # Python Tools for Visual Studio (PTVS)
312 | __pycache__/
313 | *.pyc
314 |
315 | # Cake - Uncomment if you are using it
316 | # tools/**
317 | # !tools/packages.config
318 |
319 | # Tabs Studio
320 | *.tss
321 |
322 | # Telerik's JustMock configuration file
323 | *.jmconfig
324 |
325 | # BizTalk build output
326 | *.btp.cs
327 | *.btm.cs
328 | *.odx.cs
329 | *.xsd.cs
330 |
331 | # OpenCover UI analysis results
332 | OpenCover/
333 |
334 | # Azure Stream Analytics local run output
335 | ASALocalRun/
336 |
337 | # MSBuild Binary and Structured Log
338 | *.binlog
339 |
340 | # NVidia Nsight GPU debugger configuration file
341 | *.nvuser
342 |
343 | # MFractors (Xamarin productivity tool) working folder
344 | .mfractor/
345 |
346 | # Local History for Visual Studio
347 | .localhistory/
348 |
349 | # BeatPulse healthcheck temp database
350 | healthchecksdb
351 |
352 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
353 | MigrationBackup/
354 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.{build}
2 | image: Visual Studio 2019
3 | configuration: Release
4 | install:
5 | - ps: >-
6 | cd src
7 |
8 | nuget restore UnityNuGet.sln
9 |
10 | $env:DEPLOY_SERVER_TO_AZURE = 'false'
11 |
12 | if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
13 | if($env:appveyor_repo_tag -eq 'True') {
14 | if($env:appveyor_repo_tag_name -match '^[0-9]') {
15 | $env:DEPLOY_SERVER_TO_AZURE = 'true'
16 | }
17 | }
18 | }
19 | build:
20 | project: src/UnityNuGet.sln
21 | verbosity: minimal
22 | after_build:
23 | - dotnet publish -o ..\build -c Release UnityNuGet.Server\UnityNuGet.Server.csproj
24 | artifacts:
25 | - path: src/build
26 | name: UnityNuGetDeploy
27 | type: WebDeployPackage
28 | deploy:
29 | - provider: WebDeploy
30 | server: https://unitynuget-registry.scm.azurewebsites.net:443/msdeploy.axd?site=unitynuget-registry
31 | website: unitynuget-registry
32 | username: $unitynuget-registry
33 | password:
34 | secure: j5rB9C0QezfJlpCglmalS14+5PkTuAJ1PT9fuRFNss+5UQNAPmHcW8z4pvX1CRD6htRaorT6fe+KoySIiOwYOg==
35 | remove_files: false
36 | app_offline: true
37 | artifact: UnityNuGetDeploy
38 | on:
39 | branch: master
40 | DEPLOY_SERVER_TO_AZURE: true
41 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.3.0 (10 July 2019)
4 | - Fix Guid generated for asmdef/meta files to use a stable GUID across package versions for the same asmdef
5 |
6 | ## 0.2.0 (08 July 2019)
7 | - Add System.Memory and System.Buffers (PR #2)
8 |
9 | ## 0.1.1 (08 July 2019)
10 | - Add route for root / to avoid 404 errors
11 |
12 | ## 0.1.0 (08 July 2019)
13 | - Add GeneticSharp (PR #1)
14 |
15 | ## none (07 July 2019)
16 | - Initial version
17 |
--------------------------------------------------------------------------------
/img/unity_editor_with_nuget.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keijiro/UnityNuGet/f0fae272be470b7a4e07f39e9f7aa89b615eabf3/img/unity_editor_with_nuget.jpg
--------------------------------------------------------------------------------
/img/unitynuget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keijiro/UnityNuGet/f0fae272be470b7a4e07f39e9f7aa89b615eabf3/img/unitynuget.png
--------------------------------------------------------------------------------
/img/unitynuget.svg:
--------------------------------------------------------------------------------
1 |
2 |
70 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019, Alexandre Mutel
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification
5 | , are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # UnityNuGet [](https://ci.appveyor.com/project/xoofx/unitynuget)
2 |
3 |
4 |
5 | This project provides a seamlessly integration of a [curated list](registry.json) of NuGet packages within the Unity Package Manager.
6 |
7 | > DISCLAIMER: This is not an official service provided by Unity Technologies Inc.
8 |
9 | In order to use this service you simply need to edit the `Packages/manifest.json` in your project and add the following scoped registry:
10 |
11 | ```json
12 | {
13 | "scopedRegistries": [
14 | {
15 | "name": "Unity NuGet",
16 | "url": "https://unitynuget-registry.azurewebsites.net",
17 | "scopes": [
18 | "org.nuget"
19 | ]
20 | }
21 | ],
22 | "dependencies": {
23 | "org.nuget.scriban": "2.1.0"
24 | }
25 | }
26 | ```
27 |
28 | When opening the Package Manager Window, you should see a few packages coming from NuGet (with the postfix text ` (NuGet)`)
29 |
30 | 
31 |
32 | ## Adding a package to the registry
33 |
34 | This service provides only a [curated list](registry.json) of NuGet packages
35 |
36 | Your NuGet package needs to respect a few constraints in order to be listed in the curated list:
37 |
38 | - It must have non-preview versions (e.g `1.0.0` but not `1.0.0-preview.1`)
39 | - It must provide `.NETStandard2.0` assemblies as part of its package
40 |
41 | You can send a PR to this repository to modify the [registry.json](registry.json) file (don't forget to maintain the alphabetical order)
42 |
43 | I recommend also to **specify the lowest version of your package that has support for `.NETStandard2.0`** upward so that other packages depending on your package have a chance to work with.
44 |
45 | Beware that **all transitive dependencies of the package** must be **explicitly listed** in the registry as well.
46 |
47 | > NOTE: We reserve the right to decline a package to be available through this service
48 |
49 | ## Compatibility
50 |
51 | Only compatible with **`Unity 2019.1`** and potentially with newer version.
52 |
53 | > NOTE: This service is currently only tested with **`Unity 2019.1`**
54 | >
55 | > It may not work with a more recent version of Unity
56 |
57 | ## FAQ
58 |
59 | ### Where is hosted this service?
60 |
61 | On Azure through my own Azure credits coming from my MVP subscription, enjoy!
62 |
63 | ### Why can't you add all NuGet packages?
64 |
65 | The reason is that many NuGet packages are not compatible with Unity, or do not provide .NETStandard2.0 assemblies or are not relevant for being used within Unity.
66 |
67 | Also currently the Package Manager doesn't provide a way to filter easily packages, so the UI is currently not adequate to list lots of packages.
68 |
69 | ### Why does it require .NETStandard2.0?
70 |
71 | Since 2019.1, Unity is now compatible with `.NETStandard2.0` and it is the .NET profile that is preferred to be used
72 |
73 | Having a `.NETStandard2.0` for NuGet packages for Unity can ensure that the experience to add a package to your project is consistent and well supported.
74 |
75 | ### How this service is working?
76 |
77 | This project implements a simplified compatible NPM server in C# using ASP.NET Core and converts NuGet packages to Unity packages before serving them.
78 |
79 | Every 10min, packages are updated from NuGet so that if a new version is published, from the curated list of NuGet packages, it will be available through this service.
80 |
81 | Once converted, these packages are cached on the disk on the server.
82 |
83 | ## License
84 |
85 | This software is released under the [BSD-Clause 2 license](https://opensource.org/licenses/BSD-2-Clause).
86 |
87 | ## Author
88 |
89 | Alexandre Mutel aka [xoofx](http://xoofx.com)
90 |
--------------------------------------------------------------------------------
/registry.json:
--------------------------------------------------------------------------------
1 | {
2 | "GeneticSharp": {
3 | "listed": true,
4 | "version": "2.0.0"
5 | },
6 | "Microsoft.Azure.Kinect.Sensor": {
7 | "listed": true,
8 | "version": "1.2.0"
9 | },
10 | "Microsoft.CSharp": {
11 | "ignore": true
12 | },
13 | "Newtonsoft.Json": {
14 | "listed": true,
15 | "version": "11.0.1"
16 | },
17 | "Scriban": {
18 | "listed": true,
19 | "version": "2.1.0"
20 | },
21 | "System.Buffers": {
22 | "listed": true,
23 | "version": "4.4.0"
24 | },
25 | "System.Memory": {
26 | "listed": true,
27 | "version": "4.5.0"
28 | },
29 | "System.Numerics.Vectors": {
30 | "listed": false,
31 | "version": "4.4.0"
32 | },
33 | "System.Runtime.CompilerServices.Unsafe": {
34 | "listed": true,
35 | "version": "4.4.0"
36 | },
37 | "System.Threading.Tasks.Extensions": {
38 | "listed": false,
39 | "version": "4.4.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/ApplicationBuilderExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.Extensions.Logging;
5 |
6 | namespace UnityNuGet.Server
7 | {
8 | public static class ApplicationBuilderExtensions
9 | {
10 | ///
11 | /// Logs all request and response headers (used only in development)
12 | ///
13 | public static void LogRequestHeaders(this IApplicationBuilder app, ILoggerFactory loggerFactory)
14 | {
15 | var logger = loggerFactory.CreateLogger("Request Headers");
16 | app.Use(async (context, next) =>
17 | {
18 | var builder = new StringBuilder(Environment.NewLine);
19 | foreach (var header in context.Request.Headers)
20 | {
21 | builder.AppendLine($"{header.Key}:{header.Value}");
22 | }
23 | logger.LogInformation("Request: " + builder.ToString());
24 | await next.Invoke();
25 |
26 | builder.Length = 0;
27 | builder.AppendLine();
28 | foreach (var header in context.Response.Headers)
29 | {
30 | builder.AppendLine($"{header.Key}:{header.Value}");
31 | }
32 | logger.LogInformation("Response: " + builder.ToString());
33 | });
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/Connected Services/Application Insights/ConnectedService.json:
--------------------------------------------------------------------------------
1 | {
2 | "ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider",
3 | "Version": "9.1.429.1",
4 | "GettingStartedDocument": {
5 | "Uri": "https://go.microsoft.com/fwlink/?LinkID=798432"
6 | }
7 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/Controllers/ApiController.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Microsoft.AspNetCore.Mvc;
3 | using UnityNuGet.Npm;
4 |
5 | namespace UnityNuGet.Server.Controllers
6 | {
7 | ///
8 | /// Main entry to emulate the following NPM endpoints:
9 | ///
10 | /// - "-/all": query all packages (return json)
11 | /// - "{packageId}": query a specific package (return json)
12 | /// - "{package_id}/-/{package_file}": download a specific package
13 | ///
14 | [Route("")]
15 | [ApiController]
16 | public class ApiController : ControllerBase
17 | {
18 | private readonly RegistryCacheSingleton _cacheSingleton;
19 |
20 | public ApiController(RegistryCacheSingleton cacheSingleton)
21 | {
22 | _cacheSingleton = cacheSingleton;
23 | }
24 |
25 | // GET /
26 | [HttpGet("")]
27 | public IActionResult Home()
28 | {
29 | return Ok();
30 | }
31 |
32 | // GET -/all
33 | [HttpGet("-/all")]
34 | public JsonResult GetAll()
35 | {
36 | var result = _cacheSingleton.Instance.All();
37 | return new JsonResult(result);
38 | }
39 |
40 | // GET {packageId}
41 | [HttpGet("{id}")]
42 | public JsonResult GetPackage(string id)
43 | {
44 | var package = _cacheSingleton.Instance.GetPackage(id);
45 | if (package == null)
46 | {
47 | return new JsonResult(NpmError.NotFound);
48 | }
49 |
50 | return new JsonResult(package);
51 | }
52 |
53 | // GET {package_id}/-/{package_file}
54 | [HttpGet("{id}/-/{file}")]
55 | [HttpHead("{id}/-/{file}")]
56 | public IActionResult DownloadPackage(string id, string file)
57 | {
58 | var package = _cacheSingleton.Instance.GetPackage(id);
59 | if (package == null)
60 | {
61 | return new JsonResult(NpmError.NotFound);
62 | }
63 |
64 | if (!file.StartsWith(id + "-") || !file.EndsWith(".tgz"))
65 | {
66 | return new JsonResult(NpmError.NotFound);
67 | }
68 |
69 | var filePath = _cacheSingleton.Instance.GetPackageFilePath(file);
70 | if (!System.IO.File.Exists(filePath))
71 | {
72 | return new JsonResult(NpmError.NotFound);
73 | }
74 |
75 | // This method can be called with HEAD request, so in that case we just calculate the content length
76 | if (Request.Method.Equals("HEAD"))
77 | {
78 | Response.ContentType = "application/octet-stream";
79 | Response.ContentLength = new FileInfo(filePath).Length;
80 | return Ok();
81 | }
82 | else
83 | {
84 | return new PhysicalFileResult(filePath, "application/octet-stream") {FileDownloadName = file};
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/NuGetRedirectLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Microsoft.Extensions.Logging;
4 | using NuGet.Common;
5 | using ILogger = Microsoft.Extensions.Logging.ILogger;
6 | using LogLevel = NuGet.Common.LogLevel;
7 |
8 | namespace UnityNuGet.Server
9 | {
10 | ///
11 | /// A NuGet logger redirecting to a
12 | ///
13 | public class NuGetRedirectLogger : LoggerBase
14 | {
15 | private readonly ILogger _logger;
16 |
17 | public NuGetRedirectLogger(ILogger logger)
18 | {
19 | _logger = logger ?? throw new ArgumentNullException(nameof(logger));
20 | }
21 |
22 | public override void Log(ILogMessage message)
23 | {
24 | LoggerExtensions.Log(_logger, GetLogLevel(message.Level), message.Message);
25 | }
26 |
27 | public override Task LogAsync(ILogMessage message)
28 | {
29 | LoggerExtensions.Log(_logger, GetLogLevel(message.Level), message.Message);
30 | return Task.CompletedTask;
31 | }
32 |
33 | private static Microsoft.Extensions.Logging.LogLevel GetLogLevel(LogLevel logLevel)
34 | {
35 | switch (logLevel)
36 | {
37 | case LogLevel.Debug:
38 | return Microsoft.Extensions.Logging.LogLevel.Debug;
39 | case LogLevel.Verbose:
40 | return Microsoft.Extensions.Logging.LogLevel.Trace;
41 | case LogLevel.Information:
42 | return Microsoft.Extensions.Logging.LogLevel.Information;
43 | case LogLevel.Minimal:
44 | return Microsoft.Extensions.Logging.LogLevel.Information;
45 | case LogLevel.Warning:
46 | return Microsoft.Extensions.Logging.LogLevel.Warning;
47 | case LogLevel.Error:
48 | return Microsoft.Extensions.Logging.LogLevel.Error;
49 | default:
50 | throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null);
51 | }
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using Microsoft.AspNetCore.Hosting;
3 |
4 | namespace UnityNuGet.Server
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateWebHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
14 | WebHost.CreateDefaultBuilder(args)
15 | .UseApplicationInsights()
16 | //.UseSetting("detailedErrors", "true")
17 | .UseStartup();
18 | }
19 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "UnityNuGetServer": {
5 | "commandName": "Project",
6 | "launchBrowser": true,
7 | "launchUrl": "-/all",
8 | "applicationUrl": "http://localhost",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development"
11 | }
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/RegistryCacheSingleton.cs:
--------------------------------------------------------------------------------
1 | namespace UnityNuGet.Server
2 | {
3 | public sealed class RegistryCacheSingleton
4 | {
5 | public RegistryCacheSingleton(RegistryCache instance)
6 | {
7 | Instance = instance;
8 | }
9 |
10 | public RegistryCache Instance { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/RegistryCacheUpdater.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.Hosting;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace UnityNuGet.Server
9 | {
10 | ///
11 | /// Update the RegistryCache at a regular internal (default is 10min)
12 | ///
13 | internal sealed class RegistryCacheUpdater : IHostedService, IDisposable
14 | {
15 | private readonly RegistryCacheSingleton _currentRegistryCache;
16 | private readonly ILogger _logger;
17 | private Timer _timer;
18 | // Update the RegistryCache very 10min
19 | private static readonly TimeSpan DefaultIntervalUpdate = TimeSpan.FromMinutes(10);
20 |
21 | public RegistryCacheUpdater(RegistryCacheSingleton currentRegistryCache, ILogger logger)
22 | {
23 | _currentRegistryCache = currentRegistryCache;
24 | _logger = logger;
25 | }
26 |
27 | public Task StartAsync(CancellationToken cancellationToken)
28 | {
29 | _timer = new Timer(DoWork, null, DefaultIntervalUpdate, DefaultIntervalUpdate);
30 | return Task.CompletedTask;
31 | }
32 |
33 | private async void DoWork(object state)
34 | {
35 | try
36 | {
37 | _logger.LogInformation("Starting to update RegistryCache");
38 |
39 | var previousRegistryCache = _currentRegistryCache.Instance;
40 | Debug.Assert(previousRegistryCache != null);
41 | var newRegistryCache = new RegistryCache(previousRegistryCache.RootUnityPackageFolder, previousRegistryCache.RootHttpUrl, previousRegistryCache.Logger);
42 | await newRegistryCache.Build();
43 |
44 | if (newRegistryCache.HasErrors)
45 | {
46 | _logger.LogInformation("RegistryCache not updated due to errors. See previous logs");
47 | }
48 | else
49 | {
50 | // Update the registry cache in the services
51 | _currentRegistryCache.Instance = newRegistryCache;
52 |
53 | _logger.LogInformation("RegistryCache successfully updated");
54 | }
55 | }
56 | catch (Exception ex)
57 | {
58 | _logger.LogError($"Error while building a new registry cache. Reason: {ex}");
59 | }
60 | }
61 |
62 | public Task StopAsync(CancellationToken cancellationToken)
63 | {
64 | _timer?.Change(Timeout.Infinite, 0);
65 | return Task.CompletedTask;
66 | }
67 |
68 | public void Dispose()
69 | {
70 | _timer?.Dispose();
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/Startup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using Microsoft.AspNetCore.Builder;
5 | using Microsoft.AspNetCore.Hosting;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Configuration;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace UnityNuGet.Server
12 | {
13 | public class Startup
14 | {
15 | public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory)
16 | {
17 | Configuration = configuration;
18 | HostingEnvironment = hostingEnvironment;
19 | LoggerFactory = loggerFactory;
20 | }
21 |
22 | public IConfiguration Configuration { get; }
23 |
24 | public IHostingEnvironment HostingEnvironment { get; }
25 |
26 | public ILoggerFactory LoggerFactory { get; }
27 |
28 | // This method gets called by the runtime. Use this method to add services to the container.
29 | public void ConfigureServices(IServiceCollection services)
30 | {
31 | var loggerRedirect = new NuGetRedirectLogger(LoggerFactory.CreateLogger("NuGet"));
32 |
33 | string url = "https://unitynuget-registry.azurewebsites.net/";
34 |
35 | bool isDevelopment = HostingEnvironment.IsDevelopment();
36 | if (isDevelopment)
37 | {
38 | var urls = Configuration[WebHostDefaults.ServerUrlsKey];
39 |
40 | // Select HTTPS in production, HTTP in development
41 | url = urls.Split(';').FirstOrDefault(x => !x.StartsWith("https"));
42 | if (url == null)
43 | {
44 | throw new InvalidOperationException($"Unable to find a proper server URL from `{urls}`. Expecting a `http://...` URL in development");
45 | }
46 | }
47 |
48 | // Get the current directory /home/site/unity_packages or binary folder in dev
49 | var currentDirectory = isDevelopment ? Path.GetDirectoryName(typeof(Startup).Assembly.Location) : Directory.GetCurrentDirectory();
50 | var unityPackageFolder = Path.Combine(currentDirectory, "unity_packages");
51 | loggerRedirect.LogInformation($"Using Unity Package folder `{unityPackageFolder}`");
52 |
53 | // Build the cache synchronously because ConfigureServices doesn't support async Task
54 | var initialCache = new RegistryCache(unityPackageFolder, url, loggerRedirect);
55 | initialCache.Build().GetAwaiter().GetResult();
56 |
57 | // Add the cache accessible from the services
58 | var singletonCache = new RegistryCacheSingleton(initialCache);
59 | services.AddSingleton(singletonCache);
60 |
61 | // Add the registry cache updater
62 | services.AddHostedService();
63 |
64 | // what is that?
65 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
66 | }
67 |
68 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
69 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
70 | {
71 | if (env.IsDevelopment())
72 | {
73 | app.UseDeveloperExceptionPage();
74 | app.LogRequestHeaders(LoggerFactory);
75 | }
76 | else
77 | {
78 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
79 | app.UseHsts();
80 | app.UseHttpsRedirection();
81 | }
82 | app.UseMvc();
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/UnityNuGet.Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | /subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/unitynuget-registry
6 | /subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/unitynuget-registry
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/UnityNuGet.Server/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Warning"
5 | }
6 | },
7 | "AllowedHosts": "*",
8 | "ApplicationInsights": {
9 | "InstrumentationKey": "e1cc41b5-90d8-4827-a639-4597b122c852"
10 | }
11 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Tests/RegistryCacheTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Threading.Tasks;
3 | using NUnit.Framework;
4 |
5 | namespace UnityNuGet.Tests
6 | {
7 | public class RegistryCacheTests
8 | {
9 | [Test]
10 | public void TestRegistry()
11 | {
12 | var registry = Registry.GetInstance();
13 | Assert.True(registry.Count >= 3);
14 | Assert.True(registry.ContainsKey("Scriban"));
15 | }
16 |
17 | [Test]
18 | public async Task TestBuild()
19 | {
20 | var unityPackages = Path.Combine(Path.GetDirectoryName(typeof(RegistryCacheTests).Assembly.Location), "unity_packages");
21 | var registryCache = new RegistryCache(unityPackages, "http://localhost");
22 |
23 | await registryCache.Build();
24 |
25 | Assert.False(registryCache.HasErrors, "The registry failed to build, check the logs");
26 |
27 | var allResult = registryCache.All();
28 | Assert.True(allResult.Packages.Count >= 3);
29 | var allResultJson = allResult.ToJson();
30 |
31 | StringAssert.Contains("org.nuget.scriban", allResultJson);
32 | StringAssert.Contains("org.nuget.system.runtime.compilerservices.unsafe", allResultJson);
33 |
34 | var scribanPackage = registryCache.GetPackage("org.nuget.scriban");
35 | Assert.NotNull(scribanPackage);
36 | var scribanPackageJson = scribanPackage.ToJson();
37 | StringAssert.Contains("org.nuget.scriban", scribanPackageJson);
38 | StringAssert.Contains("2.1.0", scribanPackageJson);
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/UnityNuGet.Tests/UnityNuGet.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/UnityNuGet.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityNuGet.Server", "UnityNuGet.Server\UnityNuGet.Server.csproj", "{C84FC40C-94EE-4314-BB48-CD1A98849D82}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityNuGet", "UnityNuGet\UnityNuGet.csproj", "{3E72FFD2-4CB6-4B44-9A49-29699722D93E}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnityNuGet.Tests", "UnityNuGet.Tests\UnityNuGet.Tests.csproj", "{EBC6FFF4-FD7C-4E03-B907-F12908C1D900}"
8 | EndProject
9 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D2A040A5-DB8C-43BE-A801-107503057C2B}"
10 | ProjectSection(SolutionItems) = preProject
11 | ..\appveyor.yml = ..\appveyor.yml
12 | ..\changelog.md = ..\changelog.md
13 | ..\license.txt = ..\license.txt
14 | ..\readme.md = ..\readme.md
15 | ..\registry.json = ..\registry.json
16 | EndProjectSection
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {C84FC40C-94EE-4314-BB48-CD1A98849D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {C84FC40C-94EE-4314-BB48-CD1A98849D82}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {C84FC40C-94EE-4314-BB48-CD1A98849D82}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {C84FC40C-94EE-4314-BB48-CD1A98849D82}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {3E72FFD2-4CB6-4B44-9A49-29699722D93E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {3E72FFD2-4CB6-4B44-9A49-29699722D93E}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {3E72FFD2-4CB6-4B44-9A49-29699722D93E}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {3E72FFD2-4CB6-4B44-9A49-29699722D93E}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {EBC6FFF4-FD7C-4E03-B907-F12908C1D900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {EBC6FFF4-FD7C-4E03-B907-F12908C1D900}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {EBC6FFF4-FD7C-4E03-B907-F12908C1D900}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {EBC6FFF4-FD7C-4E03-B907-F12908C1D900}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/src/UnityNuGet/JsonCommonExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Converters;
5 | using NuGet.Versioning;
6 |
7 | namespace UnityNuGet
8 | {
9 | ///
10 | /// Extension methods for serializing NPM JSON responses.
11 | ///
12 | public static class JsonCommonExtensions
13 | {
14 | public static string ToJson(this JsonObjectBase self) => JsonConvert.SerializeObject(self, Settings);
15 |
16 | ///
17 | /// Settings used for serializing JSON objects in this project.
18 | ///
19 | public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
20 | {
21 | MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
22 | DateParseHandling = DateParseHandling.None,
23 | Converters =
24 | {
25 | new IsoDateTimeConverter {
26 | DateTimeStyles = DateTimeStyles.AssumeUniversal
27 | },
28 | new VersionConverter(),
29 | },
30 | };
31 |
32 | ///
33 | /// Converter for NuGet
34 | ///
35 | private class VersionConverter : JsonConverter
36 | {
37 | public override void WriteJson(JsonWriter writer, VersionRange value, JsonSerializer serializer)
38 | {
39 | writer.WriteValue(value.ToString());
40 | }
41 |
42 | public override VersionRange ReadJson(JsonReader reader, Type objectType, VersionRange existingValue, bool hasExistingValue, JsonSerializer serializer)
43 | {
44 | string s = (string)reader.Value;
45 | return VersionRange.Parse(s);
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/JsonObjectBase.cs:
--------------------------------------------------------------------------------
1 | namespace UnityNuGet
2 | {
3 | ///
4 | /// Base object for all JSON objects serialized by this project.
5 | ///
6 | public abstract class JsonObjectBase
7 | {
8 | }
9 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmDistribution.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Newtonsoft.Json;
3 |
4 | namespace UnityNuGet.Npm
5 | {
6 | ///
7 | /// Describes how a NPM package is distributed, used by
8 | ///
9 | public class NpmDistribution : NpmObject
10 | {
11 | [JsonProperty("tarball")]
12 | public Uri Tarball { get; set; }
13 |
14 | [JsonProperty("shasum")]
15 | public string Shasum { get; set; }
16 | }
17 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmError.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace UnityNuGet.Npm
4 | {
5 | ///
6 | /// A simple object to return npm errors. Used mainly for returning
7 | ///
8 | public class NpmError : NpmObject
9 | {
10 | public static readonly NpmError NotFound = new NpmError("not_found", "document not found");
11 |
12 | public NpmError(string error, string reason)
13 | {
14 | Error = error;
15 | Reason = reason;
16 | }
17 |
18 | [JsonProperty("error")]
19 | public string Error { get; }
20 |
21 | [JsonProperty("reason")]
22 | public string Reason { get; }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmObject.cs:
--------------------------------------------------------------------------------
1 | namespace UnityNuGet.Npm
2 | {
3 | ///
4 | /// Base object for NpmObjects.
5 | ///
6 | public abstract class NpmObject : JsonObjectBase
7 | {
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmPackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 |
5 | namespace UnityNuGet.Npm
6 | {
7 | ///
8 | /// Describes a full NPM package (used as a response to `{packageId}`)
9 | ///
10 | public class NpmPackage : NpmObject
11 | {
12 | public NpmPackage()
13 | {
14 | Revision = "1-0";
15 | DistTags = new Dictionary();
16 | Versions = new Dictionary();
17 | Time = new Dictionary();
18 | Users = new Dictionary();
19 | }
20 |
21 | [JsonProperty("_id")]
22 | public string Id { get; set; }
23 |
24 | [JsonProperty("_rev")]
25 | public string Revision { get; set; }
26 |
27 | [JsonProperty("name")]
28 | public string Name { get; set; }
29 |
30 | [JsonProperty("license")]
31 | public string License { get; set; }
32 |
33 | [JsonProperty("description")]
34 | public string Description { get; set; }
35 |
36 | [JsonProperty("dist-tags")]
37 | public Dictionary DistTags { get; }
38 |
39 | [JsonProperty("versions")]
40 | public Dictionary Versions { get; }
41 |
42 | [JsonProperty("time")]
43 | public Dictionary Time { get; }
44 |
45 | [JsonProperty("repository", NullValueHandling = NullValueHandling.Ignore)]
46 | public NpmSourceRepository Repository { get; set; }
47 |
48 | [JsonProperty("users")]
49 | public Dictionary Users { get; }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmPackageInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 |
5 | namespace UnityNuGet.Npm
6 | {
7 | ///
8 | /// Describes a package for "all" listing, used by
9 | ///
10 | public class NpmPackageInfo : NpmObject
11 | {
12 | public NpmPackageInfo()
13 | {
14 | Maintainers = new List();
15 | Versions = new Dictionary();
16 | Keywords = new List();
17 | }
18 |
19 | [JsonProperty("name")]
20 | public string Name { get; set; }
21 |
22 | [JsonProperty("description")]
23 | public string Description { get; set; }
24 |
25 | [JsonProperty("maintainers")]
26 | public List Maintainers { get; }
27 |
28 | [JsonProperty("versions")]
29 | public Dictionary Versions { get; }
30 |
31 | [JsonProperty("time")]
32 | public DateTimeOffset? Time { get; set; }
33 |
34 | [JsonProperty("keywords")]
35 | public List Keywords { get; }
36 |
37 | [JsonProperty("author")]
38 | public string Author { get; set; }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmPackageListAllResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 |
5 | namespace UnityNuGet.Npm
6 | {
7 | ///
8 | /// Represents a `-/all` listing package NPM response
9 | ///
10 | ///
11 | /// NOTE: only used to serialize from C# to JSON (JSON To C# is not implemented)
12 | ///
13 | public class NpmPackageListAllResponse : NpmObject
14 | {
15 | public NpmPackageListAllResponse()
16 | {
17 | Unused = 99999;
18 | Packages = new Dictionary();
19 | }
20 |
21 | [JsonProperty("_updated")]
22 | public int Unused { get; set; }
23 |
24 | [JsonIgnore]
25 | public Dictionary Packages { get; }
26 |
27 | // everything else gets stored here
28 | [JsonExtensionData]
29 | private IDictionary AdditionalData
30 | {
31 | get
32 | {
33 | var marshalPackages = new Dictionary();
34 | foreach (var packagePair in Packages)
35 | {
36 | marshalPackages.Add(packagePair.Key, JObject.FromObject(packagePair.Value));
37 | }
38 |
39 | return marshalPackages;
40 | }
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmPackageRegistry.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace UnityNuGet.Npm
4 | {
5 | ///
6 | /// Used to store all type of responses (all packages, or single packages but also unlisted packages)
7 | ///
8 | public class NpmPackageRegistry : NpmObject
9 | {
10 | public NpmPackageRegistry()
11 | {
12 | Packages = new Dictionary();
13 | ListedPackageInfos = new NpmPackageListAllResponse();
14 | UnlistedPackageInfos = new NpmPackageListAllResponse();
15 | }
16 |
17 | public Dictionary Packages { get; }
18 |
19 | public NpmPackageListAllResponse ListedPackageInfos { get; }
20 |
21 | public NpmPackageListAllResponse UnlistedPackageInfos { get; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmPackageVersion.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 |
4 | namespace UnityNuGet.Npm
5 | {
6 | ///
7 | /// Describes a version of a
8 | ///
9 | public class NpmPackageVersion : NpmObject
10 | {
11 | public NpmPackageVersion()
12 | {
13 | Dependencies = new Dictionary();
14 | Distribution =new NpmDistribution();
15 | Scripts = new Dictionary();
16 | }
17 |
18 | [JsonProperty("name")]
19 | public string Name { get; set; }
20 |
21 | [JsonProperty("version")]
22 | public string Version { get; set; }
23 |
24 | [JsonProperty("dist")]
25 | public NpmDistribution Distribution { get; }
26 |
27 | [JsonProperty("dependencies")]
28 | public Dictionary Dependencies { get; }
29 |
30 | [JsonProperty("_id")]
31 | public string Id { get; set; }
32 |
33 | [JsonProperty("unity", NullValueHandling = NullValueHandling.Ignore)]
34 | public string Unity { get; set; }
35 |
36 | [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)]
37 | public string Description { get; set; }
38 |
39 | [JsonProperty("displayName", NullValueHandling = NullValueHandling.Ignore)]
40 | public string DisplayName { get; set; }
41 |
42 | [JsonProperty("scripts", NullValueHandling = NullValueHandling.Ignore)]
43 | public Dictionary Scripts { get; }
44 |
45 | [JsonProperty("repository", NullValueHandling = NullValueHandling.Ignore)]
46 | public NpmSourceRepository Repository { get; set; }
47 |
48 | [JsonProperty("author", NullValueHandling = NullValueHandling.Ignore)]
49 | public string Author { get; set; }
50 | }
51 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Npm/NpmSourceRepository.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace UnityNuGet.Npm
4 | {
5 | ///
6 | /// Describes a source repository used both by and
7 | ///
8 | public partial class NpmSourceRepository : NpmObject
9 | {
10 | [JsonProperty("type")]
11 | public string Type { get; set; }
12 |
13 | [JsonProperty("url")]
14 | public string Url { get; set; }
15 |
16 | [JsonProperty("revision")]
17 | public string Revision { get; set; }
18 |
19 | public NpmSourceRepository Clone()
20 | {
21 | return (NpmSourceRepository) MemberwiseClone();
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/NuGetConsoleLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using NuGet.Common;
4 |
5 | namespace UnityNuGet
6 | {
7 | ///
8 | /// A default NuGet console logger.
9 | ///
10 | public class NuGetConsoleLogger : LoggerBase
11 | {
12 | public override void Log(ILogMessage message)
13 | {
14 | if (message.Level == LogLevel.Error)
15 | {
16 | Console.Error.WriteLine(message);
17 | }
18 | else
19 | {
20 | Console.WriteLine(message);
21 | }
22 | }
23 |
24 | public override Task LogAsync(ILogMessage message)
25 | {
26 | Log(message);
27 | return Task.CompletedTask;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/Registry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using Newtonsoft.Json;
5 |
6 | namespace UnityNuGet
7 | {
8 | ///
9 | /// Loads the `registry.json` file at startup
10 | ///
11 | public sealed class Registry : Dictionary
12 | {
13 | private const string RegistryFileName = "registry.json";
14 | private static readonly object LockRead = new object();
15 | private static Registry _registry = null;
16 |
17 | public static Registry Parse(string json)
18 | {
19 | if (json == null) throw new ArgumentNullException(nameof(json));
20 | return JsonConvert.DeserializeObject(json, JsonCommonExtensions.Settings);
21 | }
22 |
23 | public static Registry GetInstance()
24 | {
25 | lock (LockRead)
26 | {
27 | if (_registry == null)
28 | {
29 | _registry = Parse(File.ReadAllText(Path.Combine(Path.GetDirectoryName(typeof(Registry).Assembly.Location), RegistryFileName)));
30 | }
31 | }
32 | return _registry;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/RegistryCache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using ICSharpCode.SharpZipLib.GZip;
11 | using ICSharpCode.SharpZipLib.Tar;
12 | using NuGet.Common;
13 | using NuGet.Configuration;
14 | using NuGet.Frameworks;
15 | using NuGet.Packaging;
16 | using NuGet.Packaging.Core;
17 | using NuGet.Protocol;
18 | using NuGet.Protocol.Core.Types;
19 | using NuGet.Versioning;
20 | using NUglify.Html;
21 | using UnityNuGet.Npm;
22 |
23 | namespace UnityNuGet
24 | {
25 | ///
26 | /// Main class used to build the unity packages and create the NPM object responses.
27 | ///
28 | public class RegistryCache
29 | {
30 | private static readonly Encoding Utf8EncodingNoBom = new UTF8Encoding(false, false);
31 | private const string UnityScope = "org.nuget";
32 | private const string MinimumUnityVersion = "2019.1";
33 | private const string PackageNameNuGetPostFix = " (NuGet)";
34 | private readonly ISettings _settings;
35 | private readonly SourceRepository _sourceRepository;
36 | private static readonly NuGetFramework NuGetFrameworkNetStandard20 = NuGetFramework.Parse("netstandard2.0");
37 | private readonly SourceCacheContext _sourceCacheContext;
38 | private readonly Registry _registry;
39 | private readonly NpmPackageRegistry _npmPackageRegistry;
40 |
41 | public RegistryCache(string rootPersistentFolder, string rootHttpUrl, ILogger logger = null)
42 | {
43 | RootUnityPackageFolder = rootPersistentFolder ?? throw new ArgumentNullException(nameof(rootPersistentFolder));
44 | RootHttpUrl = rootHttpUrl ?? throw new ArgumentNullException(nameof(rootHttpUrl)) ;
45 |
46 | if (!Directory.Exists(RootUnityPackageFolder))
47 | {
48 | Directory.CreateDirectory(RootUnityPackageFolder);
49 | }
50 | _settings = Settings.LoadDefaultSettings(root: null);
51 | var sourceRepositoryProvider = new SourceRepositoryProvider(_settings, Repository.Provider.GetCoreV3());
52 | _sourceRepository = sourceRepositoryProvider.GetRepositories().FirstOrDefault();
53 | Logger = logger ?? new NuGetConsoleLogger();
54 | _registry = Registry.GetInstance();
55 | _sourceCacheContext = new SourceCacheContext();
56 | _npmPackageRegistry = new NpmPackageRegistry();
57 | }
58 |
59 | public bool HasErrors { get; private set; }
60 |
61 | public string RootUnityPackageFolder { get; }
62 |
63 | public string RootHttpUrl { get; }
64 |
65 | public ILogger Logger { get; }
66 |
67 | ///
68 | /// Get all packages registered.
69 | ///
70 | /// A list of packages registered
71 | public NpmPackageListAllResponse All()
72 | {
73 | return _npmPackageRegistry.ListedPackageInfos;
74 | }
75 |
76 | ///
77 | /// Get a specific package for the specified package id.
78 | ///
79 | ///
80 | /// A npm package or null if not found
81 | public NpmPackage GetPackage(string packageId)
82 | {
83 | if (packageId == null) throw new ArgumentNullException(nameof(packageId));
84 | _npmPackageRegistry.Packages.TryGetValue(packageId, out var package);
85 | return package;
86 | }
87 |
88 | ///
89 | /// Gets the path for the specified package file to download
90 | ///
91 | /// The name of the package to download
92 | /// The file path of the package on the disk
93 | public string GetPackageFilePath(string packageFileName)
94 | {
95 | if (packageFileName == null) throw new ArgumentNullException(nameof(packageFileName));
96 | packageFileName = packageFileName.Replace("/", packageFileName.Replace(".", string.Empty));
97 | var packageFilePath = Path.Combine(RootUnityPackageFolder, packageFileName);
98 | return packageFilePath;
99 | }
100 |
101 | ///
102 | /// Build the registry cache.
103 | ///
104 | public async Task Build()
105 | {
106 | try
107 | {
108 | await BuildInternal();
109 | }
110 | catch (Exception ex)
111 | {
112 | LogError($"Unexpected error {ex}");
113 | }
114 | }
115 |
116 | ///
117 | /// For each package in our registry.json, query NuGet, extract package metadata, and convert them to unity packages.
118 | ///
119 | private async Task BuildInternal()
120 | {
121 | var packageMetadataResource = _sourceRepository.GetResource();
122 |
123 | foreach (var packageDesc in _registry)
124 | {
125 | var packageName = packageDesc.Key;
126 | var packageEntry = packageDesc.Value;
127 | // A package entry is ignored but allowed in the registry (case of Microsoft.CSharp)
128 | if (packageEntry.Ignored)
129 | {
130 | continue;
131 | }
132 |
133 | var packageMetaIt = await packageMetadataResource.GetMetadataAsync(packageName, false, false, _sourceCacheContext, Logger, CancellationToken.None);
134 | var packageMetas = packageMetaIt.ToList();
135 | foreach (var packageMeta in packageMetas)
136 | {
137 | var packageIdentity = packageMeta.Identity;
138 | var packageId = packageIdentity.Id.ToLowerInvariant();
139 | var npmPackageId = $"{UnityScope}.{packageId}";
140 |
141 | if (!packageEntry.Version.Satisfies(packageMeta.Identity.Version))
142 | {
143 | continue;
144 | }
145 |
146 | PackageDependencyGroup netstd20Dependency = null;
147 |
148 | foreach (var dependencySet in packageMeta.DependencySets)
149 | {
150 | if (dependencySet.TargetFramework == NuGetFrameworkNetStandard20)
151 | {
152 | netstd20Dependency = dependencySet;
153 | break;
154 | }
155 | }
156 |
157 | if (netstd20Dependency == null)
158 | {
159 | Logger.LogWarning($"The package `{packageIdentity}` doesn't support `netstandard2.0`");
160 | continue;
161 | }
162 |
163 | if (!_npmPackageRegistry.Packages.TryGetValue(npmPackageId, out var npmPackage))
164 | {
165 | npmPackage = new NpmPackage();
166 | _npmPackageRegistry.Packages.Add(npmPackageId, npmPackage);
167 | }
168 |
169 | // One NpmPackage (for package request)
170 |
171 | var packageInfoList = packageEntry.Listed ? _npmPackageRegistry.ListedPackageInfos : _npmPackageRegistry.UnlistedPackageInfos;
172 |
173 | if (!packageInfoList.Packages.TryGetValue(npmPackageId, out var npmPackageInfo))
174 | {
175 | npmPackageInfo = new NpmPackageInfo();
176 | packageInfoList.Packages.Add(npmPackageId, npmPackageInfo);
177 | }
178 |
179 | // Update latest version
180 | var currentVersion = packageIdentity.Version;
181 |
182 | var update = !npmPackage.DistTags.TryGetValue("latest", out var latestVersion)
183 | || (currentVersion > NuGetVersion.Parse(latestVersion));
184 |
185 |
186 | if (update)
187 | {
188 | npmPackage.DistTags["latest"] = currentVersion.ToString();
189 |
190 | npmPackageInfo.Versions.Clear();
191 | npmPackageInfo.Versions[currentVersion.ToString()] = "latest";
192 |
193 | npmPackage.Id = npmPackageId;
194 | npmPackage.License = packageMeta.LicenseMetadata?.License ?? packageMeta.LicenseUrl?.ToString();
195 |
196 | npmPackage.Name = npmPackageId;
197 | npmPackageInfo.Name = npmPackageId;
198 |
199 | npmPackage.Description = packageMeta.Description;
200 | npmPackageInfo.Description = packageMeta.Description;
201 |
202 | npmPackageInfo.Author = packageMeta.Authors;
203 | if (packageMeta.Owners != null)
204 | {
205 | npmPackageInfo.Maintainers.Clear();
206 | npmPackageInfo.Maintainers.AddRange(SplitCommaSeparatedString(packageMeta.Owners));
207 | }
208 |
209 | if (packageMeta.Tags != null)
210 | {
211 | npmPackageInfo.Keywords.Clear();
212 | npmPackageInfo.Keywords.Add("nuget");
213 | npmPackageInfo.Keywords.AddRange(SplitCommaSeparatedString(packageMeta.Tags));
214 | }
215 | }
216 |
217 | var npmVersion = new NpmPackageVersion
218 | {
219 | Id = $"{npmPackageId}@{currentVersion}",
220 | Version = currentVersion.ToString(),
221 | Name = npmPackageId,
222 | Description = packageMeta.Description,
223 | Author = npmPackageInfo.Author,
224 | DisplayName = packageMeta.Title + PackageNameNuGetPostFix
225 | };
226 | npmVersion.Distribution.Tarball = new Uri($"{RootHttpUrl}/{npmPackage.Id}/-/{GetUnityPackageFileName(packageIdentity)}");
227 | npmVersion.Unity = MinimumUnityVersion;
228 | npmPackage.Versions[npmVersion.Version] = npmVersion;
229 |
230 | bool hasDependencyErrors = false;
231 | foreach (var deps in netstd20Dependency.Packages)
232 | {
233 | var depsId = deps.Id.ToLowerInvariant();
234 |
235 | if (!_registry.TryGetValue(deps.Id, out var packageEntryDep))
236 | {
237 | LogError($"The package `{packageIdentity}` has a dependency on `{deps.Id}` which is not in the registry. You must add this dependency to the registry.json file.");
238 | hasDependencyErrors = true;
239 | }
240 | else if (packageEntryDep.Ignored)
241 | {
242 | // A package that is ignored is not declared as an explicit dependency
243 | continue;
244 | }
245 | else if (!deps.VersionRange.IsSubSetOrEqualTo(packageEntryDep.Version))
246 | {
247 | LogError($"The version range `{deps.VersionRange}` for the dependency `{deps.Id}` for the package `{packageIdentity}` doesn't match the range allowed from the registry.json: `{packageEntryDep.Version}`");
248 | hasDependencyErrors = true;
249 | }
250 |
251 | // Otherwise add the package as a dependency
252 | npmVersion.Dependencies.Add($"{UnityScope}.{depsId}", deps.VersionRange.MinVersion.ToString());
253 | }
254 |
255 | // If we don't have any dependencies error, generate the package
256 | if (!hasDependencyErrors)
257 | {
258 | await ConvertNuGetToUnityPackageIfDoesNotExist(packageIdentity, npmPackageInfo, npmVersion, packageMeta);
259 | npmPackage.Time[currentVersion.ToString()] = packageMeta.Published?.UtcDateTime ?? GetUnityPackageFileInfo(packageIdentity).CreationTimeUtc;
260 |
261 | // Copy repository info if necessary
262 | if (update)
263 | {
264 | npmPackage.Repository = npmVersion.Repository?.Clone();
265 | }
266 | }
267 | }
268 | }
269 | }
270 |
271 | ///
272 | /// Converts a NuGet package to Unity package if not already
273 | ///
274 | private async Task ConvertNuGetToUnityPackageIfDoesNotExist(PackageIdentity identity, NpmPackageInfo npmPackageInfo, NpmPackageVersion npmPackageVersion, IPackageSearchMetadata packageMeta)
275 | {
276 | if (!IsUnityPackageExists(identity))
277 | {
278 | await ConvertNuGetPackageToUnity(identity, npmPackageInfo, npmPackageVersion, packageMeta);
279 | }
280 | else
281 | {
282 | npmPackageVersion.Distribution.Shasum = ReadUnityPackageSha1(identity);
283 | }
284 | }
285 |
286 | ///
287 | /// Converts a NuGet package to a Unity package.
288 | ///
289 | private async Task ConvertNuGetPackageToUnity(PackageIdentity identity, NpmPackageInfo npmPackageInfo, NpmPackageVersion npmPackageVersion, IPackageSearchMetadata packageMeta)
290 | {
291 | var unityPackageFileName = GetUnityPackageFileName(identity);
292 | var unityPackageFilePath = Path.Combine(RootUnityPackageFolder, unityPackageFileName);
293 |
294 | Logger.LogInformation($"Converting NuGet package {identity} to Unity `{unityPackageFileName}`");
295 |
296 | var downloadResource = await _sourceRepository.GetResourceAsync(CancellationToken.None);
297 | var downloadResult = await downloadResource.GetDownloadResourceResultAsync(
298 | identity,
299 | new PackageDownloadContext(_sourceCacheContext),
300 | SettingsUtility.GetGlobalPackagesFolder(_settings),
301 | Logger, CancellationToken.None);
302 | var packageReader = downloadResult.PackageReader;
303 |
304 | // Update Repository metadata if necessary
305 | var repoMeta = packageReader.NuspecReader.GetRepositoryMetadata();
306 | if (repoMeta != null && repoMeta.Url != null && repoMeta.Commit != null && repoMeta.Type != null)
307 | {
308 | npmPackageVersion.Repository = new NpmSourceRepository()
309 | {
310 | Revision = repoMeta.Commit,
311 | Type = repoMeta.Type,
312 | Url = repoMeta.Url,
313 | };
314 | }
315 | else
316 | {
317 | npmPackageVersion.Repository = null;
318 | }
319 |
320 | try
321 | {
322 | var memStream = new MemoryStream();
323 |
324 | using (var outStream = File.Create(unityPackageFilePath))
325 | using (var gzoStream = new GZipOutputStream(outStream))
326 | using (var tarArchive = new TarOutputStream(gzoStream))
327 | {
328 | foreach (var item in await packageReader.GetLibItemsAsync(CancellationToken.None))
329 | {
330 | if (item.TargetFramework != NuGetFrameworkNetStandard20)
331 | {
332 | continue;
333 | }
334 |
335 | foreach (var file in item.Items)
336 | {
337 | var fileInUnityPackage = Path.GetFileName(file);
338 | var meta = UnityMeta.GetMetaForExtension(GetStableGuid(identity, fileInUnityPackage), Path.GetExtension(fileInUnityPackage));
339 | if (meta == null)
340 | {
341 | continue;
342 | }
343 |
344 | memStream.Position = 0;
345 | memStream.SetLength(0);
346 |
347 | var stream = packageReader.GetStream(file);
348 | stream.CopyTo(memStream);
349 | var buffer = memStream.ToArray();
350 |
351 | // write content
352 | WriteBufferToTar(tarArchive, fileInUnityPackage, buffer);
353 |
354 | // write meta file
355 | WriteTextFileToTar(tarArchive, fileInUnityPackage + ".meta", meta);
356 | }
357 | }
358 |
359 | // Write the package,json
360 | var unityPackage = CreateUnityPackage(npmPackageInfo, npmPackageVersion);
361 | var unityPackageAsJson = unityPackage.ToJson();
362 | const string packageJsonFileName = "package.json";
363 | WriteTextFileToTar(tarArchive, packageJsonFileName, unityPackageAsJson);
364 | WriteTextFileToTar(tarArchive, $"{packageJsonFileName}.meta", UnityMeta.GetMetaForExtension(GetStableGuid(identity, packageJsonFileName), ".json"));
365 |
366 | // Write the license to the package if any
367 | string license = null;
368 | string licenseUrlText = null;
369 |
370 | var licenseUrl = packageMeta.LicenseMetadata?.LicenseUrl.ToString() ?? packageMeta.LicenseUrl?.ToString();
371 | if (!string.IsNullOrEmpty(licenseUrl))
372 | {
373 | try
374 | {
375 | // Try to fetch the license from an URL
376 | using (var httpClient = new HttpClient())
377 | {
378 | licenseUrlText = await httpClient.GetStringAsync(licenseUrl);
379 | }
380 |
381 | // If the license text is HTML, try to remove all text
382 | if (licenseUrlText != null)
383 | {
384 | licenseUrlText = licenseUrlText.Trim();
385 | if (licenseUrlText.StartsWith("<"))
386 | {
387 | try
388 | {
389 | licenseUrlText = NUglify.Uglify.HtmlToText(licenseUrlText, HtmlToTextOptions.KeepStructure).Code ?? licenseUrlText;
390 | }
391 | catch
392 | {
393 | // ignored
394 | }
395 | }
396 | }
397 | }
398 | catch
399 | {
400 | // ignored
401 | }
402 | }
403 |
404 | if (!string.IsNullOrEmpty(packageMeta.LicenseMetadata?.License))
405 | {
406 | license = packageMeta.LicenseMetadata.License;
407 | }
408 |
409 | // If the license fetched from the URL is bigger, use that one to put into the file
410 | if (licenseUrlText != null && (license == null || licenseUrlText.Length > license.Length))
411 | {
412 | license = licenseUrlText;
413 | }
414 |
415 | if (!string.IsNullOrEmpty(license))
416 | {
417 | const string licenseMdFile = "License.md";
418 | WriteTextFileToTar(tarArchive, licenseMdFile, license);
419 | WriteTextFileToTar(tarArchive, $"{licenseMdFile}.meta", UnityMeta.GetMetaForExtension(GetStableGuid(identity, licenseMdFile), ".md"));
420 | }
421 | }
422 |
423 | using (var stream = File.OpenRead(unityPackageFilePath))
424 | {
425 | var sha1 = Sha1sum(stream);
426 | WriteUnityPackageSha1(identity, sha1);
427 | npmPackageVersion.Distribution.Shasum = sha1;
428 | }
429 | }
430 | catch (Exception ex)
431 | {
432 | try
433 | {
434 | File.Delete(unityPackageFilePath);
435 | }
436 | catch
437 | {
438 | // ignored
439 | }
440 |
441 | LogError($"Error while processing package `{identity}`. Reason: {ex}");
442 | }
443 | }
444 |
445 | private static Guid GetStableGuid(PackageIdentity identity, string name)
446 | {
447 | return StringToGuid(identity.Id + $"/{name}*");
448 | }
449 |
450 | private FileInfo GetUnityPackageFileInfo(PackageIdentity identity)
451 | {
452 | return new FileInfo(Path.Combine(RootUnityPackageFolder, GetUnityPackageFileName(identity)));
453 | }
454 |
455 | private static string GetUnityPackageFileName(PackageIdentity identity)
456 | {
457 | return $"{UnityScope}.{identity.Id.ToLowerInvariant()}-{identity.Version}.tgz";
458 | }
459 |
460 | private static string GetUnityPackageSha1FileName(PackageIdentity identity)
461 | {
462 | return $"{UnityScope}.{identity.Id.ToLowerInvariant()}-{identity.Version}.sha1";
463 | }
464 |
465 | private bool IsUnityPackageExists(PackageIdentity identity)
466 | {
467 | return File.Exists(Path.Combine(RootUnityPackageFolder, GetUnityPackageFileName(identity)));
468 | }
469 |
470 | private string ReadUnityPackageSha1(PackageIdentity identity)
471 | {
472 | return File.ReadAllText(Path.Combine(RootUnityPackageFolder, GetUnityPackageSha1FileName(identity)));
473 | }
474 |
475 | private void WriteUnityPackageSha1(PackageIdentity identity, string sha1)
476 | {
477 | File.WriteAllText(Path.Combine(RootUnityPackageFolder, GetUnityPackageSha1FileName(identity)), sha1);
478 | }
479 |
480 | private void WriteTextFileToTar(TarOutputStream tarOut, string filePath, string content)
481 | {
482 | if (tarOut == null) throw new ArgumentNullException(nameof(tarOut));
483 | if (filePath == null) throw new ArgumentNullException(nameof(filePath));
484 | if (content == null) throw new ArgumentNullException(nameof(content));
485 |
486 | var buffer = Utf8EncodingNoBom.GetBytes(content);
487 | WriteBufferToTar(tarOut, filePath, buffer);
488 | }
489 |
490 | private void WriteBufferToTar(TarOutputStream tarOut, string filePath, byte[] buffer)
491 | {
492 | if (tarOut == null) throw new ArgumentNullException(nameof(tarOut));
493 | if (filePath == null) throw new ArgumentNullException(nameof(filePath));
494 | if (buffer == null) throw new ArgumentNullException(nameof(buffer));
495 |
496 | filePath = filePath.Replace(@"\", "/");
497 | filePath = filePath.TrimStart('/');
498 |
499 | var tarEntry = TarEntry.CreateTarEntry("package/"+ filePath);
500 | tarEntry.Size = buffer.Length;
501 | tarOut.PutNextEntry(tarEntry);
502 | tarOut.Write(buffer, 0, buffer.Length);
503 | tarOut.CloseEntry();
504 | }
505 |
506 | private static UnityPackage CreateUnityPackage(NpmPackageInfo npmPackageInfo, NpmPackageVersion npmPackageVersion)
507 | {
508 | var unityPackage = new UnityPackage
509 | {
510 | Name = npmPackageInfo.Name,
511 | Version = npmPackageVersion.Version,
512 | Description = npmPackageInfo.Description,
513 | Unity = npmPackageVersion.Unity
514 | };
515 | unityPackage.Dependencies.AddRange(npmPackageVersion.Dependencies);
516 | unityPackage.Keywords.AddRange(npmPackageInfo.Keywords);
517 | return unityPackage;
518 | }
519 |
520 | private static Guid StringToGuid(string text)
521 | {
522 | var guid = new byte[16];
523 | var inputBytes = Encoding.UTF8.GetBytes(text);
524 | using (var algorithm = SHA1.Create())
525 | {
526 | var hash = algorithm.ComputeHash(inputBytes);
527 | Array.Copy(hash, 0, guid, 0, guid.Length);
528 | }
529 |
530 | // Follow UUID for SHA1 based GUID
531 | const int version = 5; // SHA1 (3 for MD5)
532 | guid[6] = (byte) ((guid[6] & 0x0F) | (version << 4));
533 | guid[8] = (byte) ((guid[8] & 0x3F) | 0x80);
534 | return new Guid(guid);
535 | }
536 |
537 | private static string Sha1sum(Stream stream)
538 | {
539 | using (SHA1Managed sha1 = new SHA1Managed())
540 | {
541 | var hash = sha1.ComputeHash(stream);
542 | var sb = new StringBuilder(hash.Length * 2);
543 |
544 | foreach (byte b in hash)
545 | {
546 | sb.Append(b.ToString("x2"));
547 | }
548 |
549 | return sb.ToString();
550 | }
551 | }
552 |
553 | private void LogError(string message, Exception ex = null)
554 | {
555 | Logger.LogError(message);
556 | HasErrors = true;
557 | }
558 |
559 | private static List SplitCommaSeparatedString(string input)
560 | {
561 | var list = new List();
562 | if (input == null) return list;
563 | foreach (var entry in input.Split(new[] {',', ';'}, StringSplitOptions.RemoveEmptyEntries))
564 | {
565 | list.Add(entry.Trim());
566 | }
567 |
568 | return list;
569 | }
570 | }
571 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/RegistryEntry.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using NuGet.Versioning;
3 |
4 | namespace UnityNuGet
5 | {
6 | ///
7 | /// An entry in the
8 | ///
9 | public class RegistryEntry
10 | {
11 | [JsonProperty("ignore")]
12 | public bool Ignored { get; set; }
13 |
14 | [JsonProperty("listed")]
15 | public bool Listed { get; set; }
16 |
17 | [JsonProperty("version")]
18 | public VersionRange Version { get; set; }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/UnityMeta.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Scriban;
3 |
4 | namespace UnityNuGet
5 | {
6 | ///
7 | /// Helper methods to create Unity .meta files
8 | ///
9 | internal static class UnityMeta
10 | {
11 | public static string GetMetaForExtension(Guid guid, string extension)
12 | {
13 | switch (extension)
14 | {
15 | case ".dll":
16 | return GetMetaForDll(guid);
17 |
18 | case ".pdb":
19 | break;
20 |
21 | case ".json":
22 | case ".xml":
23 | case ".txt":
24 | case ".md":
25 | return GetMetaForText(guid);
26 | }
27 |
28 | return null;
29 | }
30 |
31 | private static string GetMetaForDll(Guid guid)
32 | {
33 | const string text = @"fileFormatVersion: 2
34 | guid: {{ guid }}
35 | PluginImporter:
36 | externalObjects: {}
37 | serializedVersion: 2
38 | iconMap: {}
39 | executionOrder: {}
40 | defineConstraints:
41 | - NET_STANDARD_2_0
42 | isPreloaded: 0
43 | isOverridable: 0
44 | isExplicitlyReferenced: 0
45 | validateReferences: 1
46 | platformData:
47 | - first:
48 | '': Any
49 | second:
50 | enabled: 0
51 | settings:
52 | Exclude Editor: 0
53 | Exclude Linux: 0
54 | Exclude Linux64: 0
55 | Exclude LinuxUniversal: 0
56 | Exclude OSXUniversal: 0
57 | Exclude Win: 0
58 | Exclude Win64: 0
59 | - first:
60 | Any:
61 | second:
62 | enabled: 1
63 | settings: {}
64 | - first:
65 | Editor: Editor
66 | second:
67 | enabled: 1
68 | settings:
69 | CPU: AnyCPU
70 | DefaultValueInitialized: true
71 | OS: AnyOS
72 | - first:
73 | Facebook: Win
74 | second:
75 | enabled: 0
76 | settings:
77 | CPU: AnyCPU
78 | - first:
79 | Facebook: Win64
80 | second:
81 | enabled: 0
82 | settings:
83 | CPU: AnyCPU
84 | - first:
85 | Standalone: Linux
86 | second:
87 | enabled: 1
88 | settings:
89 | CPU: x86
90 | - first:
91 | Standalone: Linux64
92 | second:
93 | enabled: 1
94 | settings:
95 | CPU: x86_64
96 | - first:
97 | Standalone: LinuxUniversal
98 | second:
99 | enabled: 1
100 | settings: {}
101 | - first:
102 | Standalone: OSXUniversal
103 | second:
104 | enabled: 1
105 | settings:
106 | CPU: AnyCPU
107 | - first:
108 | Standalone: Win
109 | second:
110 | enabled: 1
111 | settings:
112 | CPU: AnyCPU
113 | - first:
114 | Standalone: Win64
115 | second:
116 | enabled: 1
117 | settings:
118 | CPU: AnyCPU
119 | - first:
120 | Windows Store Apps: WindowsStoreApps
121 | second:
122 | enabled: 0
123 | settings:
124 | CPU: AnyCPU
125 | userData:
126 | assetBundleName:
127 | assetBundleVariant:
128 | ";
129 | var meta = Template.Parse(text);
130 | return meta.Render(new {guid = guid.ToString("N")});
131 | }
132 |
133 | private static string GetMetaForText(Guid guid)
134 | {
135 | const string text = @"fileFormatVersion: 2
136 | guid: {{ guid }}
137 | TextScriptImporter:
138 | externalObjects: {}
139 | userData:
140 | assetBundleName:
141 | assetBundleVariant:
142 | ";
143 | var meta = Template.Parse(text);
144 | return meta.Render(new {guid = guid.ToString("N")});
145 | }
146 | }
147 | }
--------------------------------------------------------------------------------
/src/UnityNuGet/UnityNuGet.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 0.3.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | PreserveNewest
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/UnityNuGet/UnityPackage.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 | using UnityNuGet.Npm;
4 |
5 | namespace UnityNuGet
6 | {
7 | ///
8 | ///
9 | ///
10 | public class UnityPackage : JsonObjectBase
11 | {
12 | public UnityPackage()
13 | {
14 | Keywords = new List();
15 | Dependencies = new Dictionary();
16 | }
17 |
18 | [JsonProperty("name")]
19 | public string Name { get; set; }
20 |
21 | [JsonProperty("version")]
22 | public string Version { get; set; }
23 |
24 | [JsonProperty("unity")]
25 | public string Unity { get; set; }
26 |
27 | [JsonProperty("description")]
28 | public string Description { get; set; }
29 |
30 | [JsonProperty("keywords")]
31 | public List Keywords { get; }
32 |
33 | [JsonProperty("category")]
34 | public string Category { get; set; }
35 |
36 | [JsonProperty("dependencies")]
37 | public Dictionary Dependencies { get; }
38 | }
39 | }
--------------------------------------------------------------------------------
/src/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "2.2.100"
4 | }
5 | }
--------------------------------------------------------------------------------