├── src └── ExtensionGallery │ ├── Properties │ ├── debugSettings.json │ └── PublishProfiles │ │ └── vsixgallery - Web Deploy-publish.ps1 │ ├── app │ ├── app.bootstrap.js │ ├── config.js │ ├── controllers │ │ ├── devguide.js │ │ ├── feedguide.js │ │ ├── extension.js │ │ ├── home.js │ │ ├── author.js │ │ └── upload.js │ ├── views │ │ ├── upload.html │ │ ├── home.html │ │ ├── feedguide.html │ │ ├── extension.html │ │ ├── devguide.html │ │ └── templates.js │ ├── app.js │ ├── services │ │ └── gallery.js │ ├── less │ │ └── site.less │ └── index.html │ ├── wwwroot │ ├── favicon.ico │ ├── img │ │ ├── logo.png │ │ ├── default-icon.png │ │ ├── default-preview.png │ │ ├── favicon │ │ │ ├── favicon.ico │ │ │ ├── mstile-70x70.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-310x310.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-144x144.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-36x36.png │ │ │ ├── android-chrome-48x48.png │ │ │ ├── android-chrome-72x72.png │ │ │ ├── android-chrome-96x96.png │ │ │ ├── apple-touch-icon-57x57.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-72x72.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon-114x114.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-144x144.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ ├── android-chrome-manifest.json │ │ │ └── browserconfig.xml │ │ ├── visual-studio-options-dialog.png │ │ └── visual-studio-extensions-updates.png │ ├── bin │ │ └── AspNet.Loader.dll │ ├── _references.js │ ├── web.config │ ├── css │ │ └── site.86f0b00c.css │ ├── index.html │ └── js │ │ └── optimized.0334f283.js │ ├── bin │ └── profile │ │ └── startup.prof │ ├── approot │ └── src │ │ └── ExtensionGallery │ │ └── bin │ │ └── profile │ │ └── startup.prof │ ├── config.json │ ├── package.json │ ├── bower.json │ ├── Code │ ├── Package.cs │ ├── FeedWriter.cs │ ├── PackageHelper.cs │ └── VsixManifestParser.cs │ ├── Controllers │ ├── ControllerExtensions.cs │ ├── ApiController.cs │ └── FeedController.cs │ ├── project.json │ ├── ExtensionGallery.xproj │ ├── gruntfile.js │ └── Startup.cs ├── global.json ├── .gitattributes ├── ExtensionGallery.sln └── .gitignore /src/ExtensionGallery/Properties/debugSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Profiles": [] 3 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/app/app.bootstrap.js: -------------------------------------------------------------------------------- 1 | angular.bootstrap(document.querySelector("html"), ["galleryApp"]); -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test" ], 3 | "sdk": { 4 | "version": "1.0.0-beta5" 5 | } 6 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/logo.png -------------------------------------------------------------------------------- /src/ExtensionGallery/bin/profile/startup.prof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/bin/profile/startup.prof -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/default-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/default-icon.png -------------------------------------------------------------------------------- /src/ExtensionGallery/app/config.js: -------------------------------------------------------------------------------- 1 | var constants = { 2 | DEFAULT_ICON_IMAGE: "/img/default-icon.png", 3 | DEFAULT_PREVIEW_IMAGE: "/img/default-preview.png", 4 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/bin/AspNet.Loader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/bin/AspNet.Loader.dll -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/default-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/default-preview.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/favicon.ico -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-144x144.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-36x36.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-48x48.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-72x72.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/android-chrome-96x96.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/visual-studio-options-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/visual-studio-options-dialog.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/visual-studio-extensions-updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/visual-studio-extensions-updates.png -------------------------------------------------------------------------------- /src/ExtensionGallery/approot/src/ExtensionGallery/bin/profile/startup.prof: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/approot/src/ExtensionGallery/bin/profile/startup.prof -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/ExtensionGallery/master/src/ExtensionGallery/wwwroot/img/favicon/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/devguide.js: -------------------------------------------------------------------------------- 1 | galleryApp.controller('devguideController', ['$scope', '$rootScope', function ($scope, $rootScope) { 2 | 3 | $rootScope.pageTitle = "Add your extension"; 4 | 5 | window.scrollTo(0, 0); 6 | 7 | }]); 8 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/feedguide.js: -------------------------------------------------------------------------------- 1 | galleryApp.controller('feedguideController', ['$scope', '$rootScope', function ($scope, $rootScope) { 2 | 3 | $rootScope.pageTitle = "Subscribe to feed"; 4 | 5 | window.scrollTo(0, 0); 6 | 7 | }]); 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/ExtensionGallery/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Data": { 3 | "DefaultConnection": { 4 | "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-ExtensionGallery2-c4ef8870-447e-4a45-b8e9-bb2dacb1e415;Trusted_Connection=True;MultipleActiveResultSets=true" 5 | } 6 | }, 7 | "EntityFramework": { 8 | "ApplicationDbContext": { 9 | "ConnectionStringKey": "Data:DefaultConnection:ConnectionString" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | -------------------------------------------------------------------------------- /src/ExtensionGallery/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "ExtensionGallery2", 4 | "devDependencies": { 5 | "grunt": "^0.4.5", 6 | "grunt-angular-templates": "0.5.7" , 7 | "grunt-bower-task": "0.4.0", 8 | "grunt-contrib-clean": "0.6.0", 9 | "grunt-contrib-concat": "0.5.0", 10 | "grunt-contrib-copy": "0.7.0", 11 | "grunt-contrib-cssmin": "0.11.0", 12 | "grunt-contrib-htmlmin": "0.3.0", 13 | "grunt-contrib-less": "1.0.0", 14 | "grunt-contrib-uglify": "0.7.0", 15 | "grunt-contrib-watch": "0.6.1", 16 | "grunt-filerev": "2.1.2", 17 | "grunt-usemin": "3.0.0", 18 | "load-grunt-tasks": "2.0.0" 19 | } 20 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/extension.js: -------------------------------------------------------------------------------- 1 | 2 | galleryApp.controller('extensionController', ['$scope', '$rootScope', '$location', '$route', 'dataService', function ($scope, $rootScope, $location, $route, dataService) { 3 | 4 | var id = $route.current.params.id; 5 | 6 | dataService.getExtension(id, function (data) { 7 | 8 | if (data.error) { 9 | $location.path('/'); 10 | } 11 | 12 | $rootScope.pageTitle = data.Name; 13 | 14 | dataService.getMarkdown(data, function (data) { 15 | if (data.error !== true) 16 | $scope.package.readme = data; 17 | }); 18 | 19 | $scope.package = data; 20 | }); 21 | 22 | window.scrollTo(0, 0); 23 | 24 | }]); 25 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/home.js: -------------------------------------------------------------------------------- 1 | 2 | galleryApp.controller('homeController', ['$scope', '$rootScope', 'dataService', function ($scope, $rootScope, dataService) { 3 | 4 | $rootScope.pageTitle = "Open VSIX Gallery"; 5 | 6 | $scope.feed = "/feed/"; 7 | $scope.query = ''; 8 | 9 | $scope.packageSearch = function (package) { 10 | var q = $scope.query.toUpperCase(); 11 | 12 | return package.Name.toUpperCase().indexOf(q) != -1 || 13 | package.Description.toUpperCase().indexOf(q) != -1 || 14 | package.Author.toUpperCase().indexOf(q) != -1 || 15 | (package.Tags && package.Tags.toUpperCase().indexOf(q) != -1); 16 | }; 17 | 18 | dataService.getAllExtensions(function (data) { 19 | $scope.packages = data; 20 | }); 21 | 22 | }]); 23 | -------------------------------------------------------------------------------- /src/ExtensionGallery/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ExtensionGallery2", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "3.0.0", 6 | "angular": "1.3.8", 7 | "angular-route": "1.3.8", 8 | "moment": "2.10.6" 9 | }, 10 | "exportsOverride": { 11 | "angular": { 12 | "": "angular.{js,min.js,min.map}" 13 | }, 14 | "angular-route": { 15 | "": "angular-route.{js,min.js,*.map}" 16 | }, 17 | "bootstrap": { 18 | "js": "dist/js/*.*", 19 | "css": "dist/css/*.*", 20 | "fonts": "dist/fonts/*.*" 21 | }, 22 | "jquery": { 23 | "js": "jquery.{js,min.js,min.map}" 24 | }, 25 | "jquery-validation": { 26 | "": "jquery.validate.js" 27 | }, 28 | "jquery-validation-unobtrusive": { 29 | "": "jquery.validate.unobtrusive.{js,min.js}" 30 | }, 31 | "moment": { 32 | "": "min/moment.min.js" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Code/Package.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace ExtensionGallery.Code 5 | { 6 | public class Package 7 | { 8 | public string ID { get; set; } 9 | public string Name { get; set; } 10 | public string Description { get; set; } 11 | public string Author { get; set; } 12 | public string Version { get; set; } 13 | public string Icon { get; set; } 14 | public string Preview { get; set; } 15 | public string Tags { get; set; } 16 | public DateTime DatePublished { get; set; } 17 | public IEnumerable SupportedVersions { get; set; } 18 | public string License { get; set; } 19 | public string GettingStartedUrl { get; set; } 20 | public string ReleaseNotesUrl { get; set; } 21 | public string MoreInfoUrl { get; set; } 22 | public string Repo { get; set; } 23 | public string IssueTracker { get; set; } 24 | 25 | public override string ToString() 26 | { 27 | return Name; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/android-chrome-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My app", 3 | "icons": [ 4 | { 5 | "src": "\/img\/favicon\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/img\/favicon\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/img\/favicon\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/img\/favicon\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/img\/favicon\/android-chrome-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/img\/favicon\/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/author.js: -------------------------------------------------------------------------------- 1 | 2 | galleryApp.controller('authorController', ['$scope', '$rootScope', '$route', 'dataService', function ($scope, $rootScope, $route, dataService) { 3 | 4 | $rootScope.pageTitle = "Extensions by " + toTitleCase($route.current.params.name); 5 | 6 | $scope.feed = "/feed/author/" + $route.current.params.name + "/"; 7 | $scope.query = ''; 8 | 9 | 10 | $scope.packageSearch = function (package) { 11 | var q = $scope.query.toUpperCase(); 12 | 13 | return package.Name.toUpperCase().indexOf(q) != -1 || 14 | package.Description.toUpperCase().indexOf(q) != -1 || 15 | package.Author.toUpperCase().indexOf(q) != -1 || 16 | (package.Tags && package.Tags.toUpperCase().indexOf(q) != -1); 17 | }; 18 | 19 | function toTitleCase(str) { 20 | return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); 21 | } 22 | 23 | dataService.getAllExtensions(function (data) { 24 | 25 | var author = $route.current.params.name.toUpperCase() 26 | 27 | $scope.packages = data.filter(function (p) { return p.Author.toUpperCase() === author }); 28 | }); 29 | 30 | }]); 31 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/upload.html: -------------------------------------------------------------------------------- 1 |

Upload new extension

2 |

3 | Upload a new extension to the gallery. It can either be a brand new extension or and updated version 4 | of an extension that's already on the gallery. 5 |

6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 |

Only .vsix files are supported

22 |
23 | 24 | 25 | 26 |

{{error}}

27 |
-------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/img/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #ffffff 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 30 18 | 1 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/controllers/upload.js: -------------------------------------------------------------------------------- 1 | 2 | galleryApp.controller('uploadController', ['$scope', '$rootScope', '$location', 'dataService', function ($scope, $rootScope, $location, dataService) { 3 | 4 | $rootScope.pageTitle = "Upload an extension"; 5 | 6 | $scope.error = ""; 7 | $scope.repo = ""; 8 | $scope.issuetracker = ""; 9 | 10 | if (localStorage) { 11 | $scope.repo = localStorage["upload.repo"]; 12 | $scope.issuetracker = localStorage["upload.issuetracker"]; 13 | } 14 | 15 | $scope.upload = function () { 16 | var fileInput = document.getElementById("uploadfile"); 17 | var reader = new FileReader(); 18 | reader.onload = function (result) { 19 | 20 | var query = "?repo=" + encodeURIComponent($scope.repo) + "&issuetracker=" + encodeURIComponent($scope.issuetracker); 21 | dataService.upload(result.target.result, query, function (data) { 22 | 23 | if (!data.error) 24 | $location.path('/extension/' + data.ID); 25 | else 26 | $scope.error = data; 27 | }); 28 | }; 29 | 30 | reader.readAsArrayBuffer(fileInput.files[0]); 31 | 32 | if (localStorage && localStorage["upload.repo"]) 33 | localStorage["upload.repo"] = $scope.repo; 34 | if (localStorage && localStorage["upload.issuetracker"]) 35 | localStorage["upload.issuetracker"] = $scope.issuetracker; 36 | }; 37 | 38 | }]); 39 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Controllers/ControllerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ExtensionGallery.Code; 5 | using Microsoft.AspNet.Mvc; 6 | 7 | namespace ExtensionGallery.Controllers 8 | { 9 | public static class ControllerExtensions 10 | { 11 | public static bool IsConditionalGet(this Controller controller, IEnumerable packages) 12 | { 13 | Package package = packages.FirstOrDefault(); 14 | return controller.IsConditionalGet(package); 15 | } 16 | 17 | public static bool IsConditionalGet(this Controller controller, Package package) 18 | { 19 | if (package == null) 20 | return false; 21 | 22 | string lastmod = package.DatePublished.ToString("r"); 23 | string etag = "\"" + package.DatePublished.Ticks.ToString() + "\""; 24 | 25 | controller.Response.Headers["Last-Modified"] = lastmod; 26 | controller.Response.Headers["ETag"] = etag; 27 | 28 | // Test If-None-Match 29 | if (controller.Request.Headers["If-None-Match"] != etag) 30 | { 31 | return false; 32 | } 33 | 34 | // Test Is-Modified-Since 35 | DateTime lm = package.DatePublished; 36 | DateTime ifModifiedSince; 37 | lm = new DateTime(lm.Year, lm.Month, lm.Day, lm.Hour, lm.Minute, lm.Second, DateTimeKind.Utc); 38 | 39 | if (!DateTime.TryParse(controller.Request.Headers["If-Modified-Since"], out ifModifiedSince) || lm != ifModifiedSince) 40 | { 41 | return false; 42 | } 43 | 44 | controller.Response.StatusCode = 304; 45 | 46 | return true; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Controllers/ApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using ExtensionGallery.Code; 6 | using Microsoft.AspNet.Hosting; 7 | using Microsoft.AspNet.Mvc; 8 | 9 | namespace ExtensionGallery.Controllers 10 | { 11 | public class ApiController : Controller 12 | { 13 | IHostingEnvironment _env; 14 | PackageHelper _helper; 15 | 16 | public ApiController(IHostingEnvironment env) 17 | { 18 | _env = env; 19 | _helper = new PackageHelper(env.WebRootPath); 20 | } 21 | 22 | public object Get(string id) 23 | { 24 | Response.Headers["Cache-Control"] = "no-cache"; 25 | 26 | if (string.IsNullOrWhiteSpace(id)) 27 | { 28 | var packages = _helper.PackageCache.OrderByDescending(p => p.DatePublished); 29 | 30 | if (this.IsConditionalGet(packages)) 31 | { 32 | return Enumerable.Empty(); 33 | } 34 | 35 | return packages; 36 | } 37 | 38 | var package = _helper.GetPackage(id); 39 | 40 | if (this.IsConditionalGet(package)) 41 | { 42 | return new EmptyResult(); 43 | } 44 | 45 | return package; 46 | } 47 | 48 | [HttpPost] 49 | public async Task Upload([FromQuery]string repo, string issuetracker) 50 | { 51 | try 52 | { 53 | Stream bodyStream = Context.Request.Body; 54 | Package package = await _helper.ProcessVsix(bodyStream, repo, issuetracker); 55 | 56 | return Json(package); 57 | } 58 | catch (Exception ex) 59 | { 60 | Response.StatusCode = 500; 61 | Response.Headers["x-error"] = ex.Message; 62 | return Content(ex.Message); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "webroot": "wwwroot", 3 | "version": "1.0.0-*", 4 | "dependencies": { 5 | "Microsoft.AspNet.Mvc": "6.0.0-beta5", 6 | "Microsoft.AspNet.Diagnostics": "1.0.0-beta5", 7 | "Microsoft.AspNet.Authentication.Cookies": "1.0.0-beta5", 8 | "Microsoft.AspNet.Server.IIS": "1.0.0-beta5", 9 | "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5", 10 | "Microsoft.AspNet.StaticFiles": "1.0.0-beta5", 11 | "Microsoft.Framework.Configuration.Json": "1.0.0-beta5", 12 | "Microsoft.Framework.CodeGenerators.Mvc": "1.0.0-beta5", 13 | "Microsoft.Framework.Logging": "1.0.0-beta5", 14 | "Microsoft.Framework.Logging.Console": "1.0.0-beta5" 15 | }, 16 | "commands": { 17 | "web": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener --server.urls http://localhost:5000" 18 | }, 19 | "frameworks": { 20 | "dnx451": { 21 | "dependencies": { 22 | "System.Net.Http": "4.0.0-beta-22816", 23 | "System.Xml.XmlDocument": "4.0.0-beta-22816", 24 | "System.IO.Compression.ZipFile": "4.0.0-beta-22816" 25 | } 26 | } 27 | }, 28 | "exclude": [ 29 | "wwwroot", 30 | "node_modules", 31 | "bower_components", 32 | "app", 33 | ".tmp" 34 | ], 35 | "publishExclude": [ 36 | "node_modules", 37 | "bower_components", 38 | "app", 39 | "wwwroot/extensions", 40 | "wwwroot/temp", 41 | ".tmp", 42 | "**.kproj", 43 | "**.user", 44 | "**.vspscc", 45 | "**._references.js" 46 | ], 47 | "scripts": { 48 | "postrestore": [ "npm install", "bower install", "grunt" ], 49 | "prepack": [ "grunt build" ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ExtensionGallery.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.22823.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F21BC57F-7D70-4278-BD7C-463AEEF6C889}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ExtensionGallery", "src\ExtensionGallery\ExtensionGallery.xproj", "{5092448C-B511-4077-B31C-00685E56FB2D}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C5D93B6B-F583-4464-B315-E1D13A164FF6}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitignore = .gitignore 13 | global.json = global.json 14 | EndProjectSection 15 | ProjectSection(FolderGlobals) = preProject 16 | global_1json__JSONSchema = http://json.schemastore.org/global 17 | EndProjectSection 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {5092448C-B511-4077-B31C-00685E56FB2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {5092448C-B511-4077-B31C-00685E56FB2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {5092448C-B511-4077-B31C-00685E56FB2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {5092448C-B511-4077-B31C-00685E56FB2D}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(NestedProjects) = preSolution 34 | {5092448C-B511-4077-B31C-00685E56FB2D} = {F21BC57F-7D70-4278-BD7C-463AEEF6C889} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/app.js: -------------------------------------------------------------------------------- 1 | var galleryApp = angular 2 | 3 | .module('galleryApp', ['ngRoute']) 4 | 5 | .filter('escape', function () { 6 | return window.encodeURIComponent; 7 | }) 8 | 9 | .filter('rawHtml', ['$sce', function ($sce) { 10 | return function (val) { 11 | return $sce.trustAsHtml(val); 12 | }; 13 | }]) 14 | 15 | .config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) { 16 | 17 | $locationProvider.html5Mode(true); 18 | 19 | $routeProvider 20 | .when('/', 21 | { 22 | title: 'Visual Studio Extension Gallery', 23 | controller: 'homeController', 24 | templateUrl: 'app/views/home.html' 25 | }) 26 | .when('/author/:name', 27 | { 28 | controller: 'authorController', 29 | templateUrl: 'app/views/home.html' 30 | }) 31 | .when('/extension/:id/', 32 | { 33 | controller: 'extensionController', 34 | templateUrl: 'app/views/extension.html' 35 | }) 36 | .when('/upload', 37 | { 38 | controller: 'uploadController', 39 | templateUrl: 'app/views/upload.html' 40 | }) 41 | .when('/guide/dev/', 42 | { 43 | controller: 'devguideController', 44 | templateUrl: 'app/views/devguide.html' 45 | }) 46 | .when('/guide/feed/', 47 | { 48 | controller: 'feedguideController', 49 | templateUrl: 'app/views/feedguide.html' 50 | }) 51 | .otherwise( 52 | { 53 | redirectTo: '/' 54 | }); 55 | }]); -------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/home.html: -------------------------------------------------------------------------------- 1 | 2 |

3 | {{pageTitle}} 4 | 5 | Feed 6 | 7 |

8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 | 44 |
45 |

No extensions found

46 |
47 |
-------------------------------------------------------------------------------- /src/ExtensionGallery/ExtensionGallery.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 5092448c-b511-4077-b31c-00685e56fb2d 9 | ExtensionGallery 10 | ..\..\artifacts\obj\$(MSBuildProjectName) 11 | ..\..\artifacts\bin\$(MSBuildProjectName)\ 12 | 13 | 14 | ExtensionGallery 15 | 16 | 17 | 2.0 18 | 7035 19 | 20 | 21 | False 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/css/site.86f0b00c.css: -------------------------------------------------------------------------------- 1 | html,body{height:100%}body{padding-top:60px;overflow-y:scroll}a.btn:hover,a.btn:active,a.btn:focus,a.btn-sm:hover,a.btn-sm:active,a.btn-sm:focus{text-decoration:none}.navbar-brand{background:url(/img/logo.png) no-repeat 14px center;padding-left:40px}.container{max-width:960px;min-height:100%;overflow:auto}main{overflow:auto;margin-bottom:3em;padding-bottom:155px}a{color:#2f70a9}a:focus{outline:thin}h2{font-size:1.6em;font-weight:bold}h3{font-size:1.6em;margin:0;padding:0}dd{padding:0 0 1em 1em}img{max-width:100%}#extensions{position:relative}#extensions h3 span{font-size:.6em}#extensions #searchform{width:250px;float:right}#extensions article{clear:both;padding:10px;margin-top:3em}#extensions article>div{margin-left:120px}#extensions article .author{margin:3px 0;display:inline-block}#extensions article .icon{float:left}#extensions article .preview{float:left;width:175px;margin-bottom:2em}#extensions .extension{margin-top:0}#extensions .extension ul{list-style:none;padding:0;margin:0 0 1em 0}#extensions .extension ul li{display:inline-block;margin-right:1em}#extensions .extension .properties{margin-left:200px !important}#extensions .extension .properties .details{line-height:1.3;margin:0 0 15px 0;display:inline-block}#extensions .extension .properties .details p:first-child{margin-bottom:2em}#extensions .extension .properties .details>p>strong{display:inline-block;width:80px}#extensions .readme{line-height:1.5}#extensions .readme p{margin:1.2em 0}#upload{max-width:450px;padding-left:5px}#upload input[type=file]{width:100%}#upload a[ng-click]{margin-top:1em;display:inline-block;cursor:pointer;text-decoration:none}#upload .has-error{color:red !important}footer{position:relative;margin-top:-155px;height:155px;clear:both;border-top:1px solid lightgray;background:#f1f1f1}footer ul{margin-top:1em;list-style:none;padding:0 6em 0 0}footer .copyright{text-align:right;font-size:.9em;color:#808080;clear:both}@media all and (min-width:400px) and (max-width:800px){body{min-width:620px}#extensions article p{-ms-text-size-adjust:130%}.navbar-nav{display:none}@-ms-viewport{width:620px}} -------------------------------------------------------------------------------- /src/ExtensionGallery/Controllers/FeedController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using ExtensionGallery.Code; 3 | using Microsoft.AspNet.Hosting; 4 | using Microsoft.AspNet.Mvc; 5 | 6 | namespace ExtensionGallery.Controllers 7 | { 8 | public class FeedController : Controller 9 | { 10 | PackageHelper _helper; 11 | FeedWriter _feed; 12 | 13 | public FeedController(IHostingEnvironment env) 14 | { 15 | _helper = new PackageHelper(env.WebRootPath); 16 | _feed = new FeedWriter(); 17 | } 18 | 19 | public IActionResult Index() 20 | { 21 | Response.ContentType = "text/xml"; 22 | Package[] packages = _helper.PackageCache.OrderByDescending(p => p.DatePublished).ToArray(); 23 | 24 | if (this.IsConditionalGet(packages)) 25 | { 26 | return new EmptyResult(); 27 | } 28 | 29 | string baseUrl = Request.Scheme + "://" + Request.Host; 30 | return Content(_feed.GetFeed(baseUrl, packages)); 31 | } 32 | 33 | public IActionResult Extension(string id) 34 | { 35 | Response.ContentType = "text/xml"; 36 | 37 | if (!string.IsNullOrEmpty(id)) 38 | { 39 | Package package = _helper.GetPackage(id); 40 | 41 | if (this.IsConditionalGet(package)) 42 | { 43 | return new EmptyResult(); 44 | } 45 | 46 | string baseUrl = Request.Scheme + "://" + Request.Host; 47 | return Content(_feed.GetFeed(baseUrl, package)); 48 | } 49 | 50 | return new RedirectResult("/", true); 51 | } 52 | 53 | public IActionResult Author(string id) 54 | { 55 | Response.ContentType = "text/xml"; 56 | string baseUrl = Request.Scheme + "://" + Request.Host; 57 | 58 | if (!string.IsNullOrEmpty(id)) 59 | { 60 | var packages = _helper.PackageCache 61 | .Where(p => p.Author.Equals(id, System.StringComparison.OrdinalIgnoreCase)) 62 | .OrderByDescending(p => p.DatePublished); 63 | 64 | if (this.IsConditionalGet(packages)) 65 | { 66 | return new EmptyResult(); 67 | } 68 | 69 | return Content(_feed.GetFeed(baseUrl, packages.ToArray())); 70 | } 71 | 72 | return new RedirectResult("/", true); 73 | } 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/feedguide.html: -------------------------------------------------------------------------------- 1 |

Subscribe to feed

2 | 3 |

4 | Visual Studio is capable of subscribing to extension feeds, so you will be notified 5 | of any updates to extensions found in this gallery. You will only be notified of any updates to extensions that you already have installed. 6 |

7 | 8 |
9 |

Choose the right feed

10 |

There are several feeds in this gallery you can subscribe to.

11 | 12 |
13 |
The main feed
14 |
15 | By subscribing to the main feed you will be notified by 16 | Visual Studio whenever any of the extensions in this gallery are updated. The feed 17 | contains all extensions available. 18 |
19 | 20 |
Feed per author
21 |
22 | Each extension author has their own feed, so you can choose to subsribe to one or more 23 | authors' extensions. 24 |
25 | 26 |
Feed for individual extension
27 |
28 | If you're only interested in getting updates to specific extensions, then you can subsribe 29 | to feeds for that one extension. 30 |
31 |
32 |
33 | 34 |
35 |

Setup

36 |

In Visual Studio go to Tools -> Options -> Environment -> Extensions and Updates.

37 |

Visual Studio options dialog

38 | 39 | 40 |

Click the Add button and fill in the name and URL fields.

41 |

Name: Give it a name you like

42 |

URL: Could be the main feed http://vsixgallery.com/feed/

43 |

And finally click the Apply button.

44 | 45 |

46 | That's it. You've now added the nightly feed to Visual Studio and updates will start to show up in 47 | Tools -> Extensions and Updates dialog 48 |

49 | 50 |
51 | 52 |

Extensions and Updates

53 |

54 | You can now see the updates coming in to the Tools -> Extensions and Updates dialog. 55 |

56 | 57 |

Visual Studio Extensions and Updates

58 |
59 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/extension.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{package.Name}}

4 | 5 | 46 | 47 |
48 |
49 |

50 |
51 |
52 |

License

53 |

54 |         
55 |
56 |
-------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/devguide.html: -------------------------------------------------------------------------------- 1 |

Add your extension

2 | 3 |

4 | You can add your extension to this gallery in 2 different ways as part of your build automation. 5 |

6 | 7 |
    8 |
  1. Use PowerShell
  2. 9 |
  3. Use AppVeyor
  4. 10 |
11 | 12 |

13 | Both PowerShell and AppVeyor uses a 14 | custom script 15 | that makes it easy to publish the extension to this gallery. It contains other functions that are useful for 16 | incrementing the VSIX version and other handy things. 17 |

18 | 19 |
20 |

Use PowerShell

21 | 22 |

First you must execute the VSIX script

23 | 24 |
(new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex
25 | 26 |

That allows you to call methods upload the .vsix extension file to the gallery.

27 | 28 |
Vsix-PublishToGallery
29 | 30 |

31 | That will find all .vsix files in the working directory recursively and upload them. 32 | To specify the path, simply pass it in as the first parameter: 33 |

34 | 35 |
Vsix-PublishToGallery .\src\WebCompilerVsix\**\*.vsix
36 |
37 | 38 |
39 |

Use AppVeyor

40 | 41 |

AppVeyor is a build server hosted in the cloud and it's free.

42 | 43 |

44 | After you've created an account, you can start doing automated builds. A really nice thing 45 | is that AppVeyor can automatically kick off a new build when you commit code to either 46 | GitHub, VSO or other code repositories. 47 |

48 | 49 |

50 | To automatically upload your extension to vsixgallery.com when the build has succeeded, 51 | all you have to do is to add an appveyor.yml file to the root of 52 | your repository. The content of the file should look like this: 53 |

54 | 55 |
version: 1.0.{build}
56 | 
57 | install:
58 |   - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex
59 | 
60 | before_build:
61 |   - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion
62 | 
63 | build_script:
64 |   - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m
65 | 
66 | after_test:
67 |   - ps: Vsix-PushArtifacts | Vsix-PublishToGallery
68 | 
69 | 70 |

You might want to check out these real-world uses:

71 | 72 | 78 | 79 |
80 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/services/gallery.js: -------------------------------------------------------------------------------- 1 | galleryApp.service("dataService", ["$http", function ($http) { 2 | 3 | var apiBase = "/api/", 4 | extBase = "/extensions/", 5 | cache = [], 6 | readme = {}; 7 | 8 | function normalizePackage(package) { 9 | 10 | package.DownloadUrl = extBase + package.ID + "/" + encodeURIComponent(package.Name + " v" + package.Version) + ".vsix"; 11 | 12 | if (package.Icon) { 13 | package.Icon = extBase + package.ID + '/' + package.Icon; 14 | } else { 15 | package.Icon = constants.DEFAULT_ICON_IMAGE; 16 | } 17 | 18 | if (package.Preview) { 19 | package.Preview = extBase + package.ID + '/' + package.Preview; 20 | } else { 21 | package.Preview = constants.DEFAULT_PREVIEW_IMAGE; 22 | } 23 | 24 | package.SupportedVersions = package.SupportedVersions.map(function (v) { 25 | if (v.indexOf('11.') == 0) 26 | return 2012; 27 | if (v.indexOf('12.') == 0) 28 | return 2013; 29 | if (v.indexOf('14.') == 0) 30 | return 2015; 31 | }); 32 | 33 | package.relativeDate = moment(package.DatePublished).fromNow(); 34 | 35 | return package; 36 | } 37 | 38 | this.getAllExtensions = function (callback) { 39 | 40 | if (cache.length > 0) 41 | return callback(cache); 42 | 43 | $http.get(apiBase + "get/") 44 | .success(function (data) { 45 | 46 | for (var i = 0; i < data.length; i++) { 47 | normalizePackage(data[i]); 48 | } 49 | 50 | cache = data; 51 | callback(data); 52 | }) 53 | .error(function (response) { 54 | callback({ error: true }); 55 | }); 56 | } 57 | 58 | this.getExtension = function (id, callback) { 59 | var package = cache.filter(function (p) { return p.ID === id }); 60 | 61 | if (package.length > 0) 62 | return callback(package[0]); 63 | 64 | $http.get(apiBase + "get/" + id).success(function (data) { 65 | 66 | callback(normalizePackage(data)); 67 | }); 68 | } 69 | 70 | this.upload = function (bytes, query, callback) { 71 | $http.post(apiBase + "upload" + query, new Blob([bytes], {})).success(function (data) { 72 | 73 | for (var i = 0; i < cache.length; i++) { 74 | var package = cache[i]; 75 | 76 | if (package.ID == data.ID) { 77 | cache.splice(i, 1); 78 | break; 79 | } 80 | } 81 | 82 | cache.splice(0, 0, normalizePackage(data)); 83 | 84 | callback(data); 85 | }) 86 | .error(function (response) { 87 | callback({ error: true }); 88 | });; 89 | } 90 | 91 | this.getMarkdown = function (package, callback) { 92 | 93 | if (!package.Repo) { 94 | callback({ error: true }); 95 | return; 96 | } 97 | 98 | var url = package.Repo.replace("https://github.com", "https://raw.githubusercontent.com") + "master/README.md"; 99 | if (package.Repo.indexOf("bitbucket") > -1) { 100 | url = package.Repo + "raw/tip/README.md"; 101 | } 102 | 103 | $http.get("http://markdownservice.azurewebsites.net/markdown.ashx?url=" + escape(url)) 104 | .success(function (data) { 105 | callback(data); 106 | }) 107 | .error(function (response) { 108 | callback({ error: true }); 109 | }); 110 | } 111 | 112 | }]); 113 | 114 | -------------------------------------------------------------------------------- /src/ExtensionGallery/gruntfile.js: -------------------------------------------------------------------------------- 1 | /// 2 | module.exports = function (grunt) { 3 | 4 | require("load-grunt-tasks")(grunt); 5 | 6 | var project = { 7 | dist: "wwwroot", 8 | app: "app" 9 | }; 10 | 11 | grunt.initConfig({ 12 | project: project, 13 | 14 | bower: { 15 | install: { 16 | options: { 17 | targetDir: "<%= project.dist %>/lib", 18 | layout: "byComponent", 19 | cleanTargetDir: false 20 | } 21 | } 22 | }, 23 | 24 | useminPrepare: { 25 | html: '<%= project.app %>/index.html', 26 | options: { 27 | dest: '<%= project.dist %>', 28 | staging: 'obj' 29 | } 30 | }, 31 | 32 | usemin: { 33 | html: ['<%= project.dist %>/{,*/}*.html'], 34 | css: ['<%= project.dist %>/css/{,*/}*.css'], 35 | options: { 36 | assetsDirs: ['<%= project.dist %>'], 37 | } 38 | }, 39 | 40 | ngtemplates: { 41 | galleryApp: { 42 | src: '<%= project.app %>/views/*.html', 43 | dest: '<%= project.app %>/views/templates.js', 44 | options: { 45 | htmlmin: { collapseWhitespace: true, collapseBooleanAttributes: true, removeAttributeQuotes: true } 46 | } 47 | } 48 | }, 49 | 50 | clean: ["<%= project.dist %>/css", "<%= project.dist %>/js"], 51 | 52 | less: { 53 | dev: { 54 | files: { 55 | "<%= project.dist %>/css/site.css": ["<%= project.app %>/less/**/*.less"] 56 | }, 57 | options: { 58 | compress: true, 59 | } 60 | } 61 | }, 62 | 63 | copy: { 64 | html: { 65 | files: { 66 | "<%= project.dist %>/index.html": ["app/index.html"] 67 | } 68 | } 69 | }, 70 | 71 | filerev: { 72 | dev: { 73 | src: [ 74 | '<%= project.dist %>/js/{,*/}*.js', 75 | '<%= project.dist %>/css/{,*/}*.css', 76 | '<%= project.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 77 | ] 78 | } 79 | }, 80 | 81 | watch: { 82 | html: { 83 | files: ["<%= project.app %>/index.html", "<%= project.app %>/views/*.html"], 84 | tasks: ["build"] 85 | }, 86 | js: { 87 | files: ["<%= project.app %>/**/*.js", "!<%= project.app %>/views/templates.js"], 88 | tasks: ["build"] 89 | }, 90 | less: { 91 | files: ["<%= project.app %>/less/*.less"], 92 | tasks: ["build"] 93 | }, 94 | bower: { 95 | files: ['bower.json'], 96 | tasks: ['bower:install'] 97 | }, 98 | gruntfile: { 99 | files: ["gruntfile.js"] 100 | } 101 | } 102 | }); 103 | 104 | grunt.registerTask("default", ["bower:install", "build"]); 105 | 106 | grunt.registerTask("build", [ 107 | "clean", 108 | "less", 109 | "useminPrepare", 110 | "copy:html", 111 | "ngtemplates", 112 | "concat:generated", 113 | "uglify:generated", 114 | "filerev", 115 | "usemin", 116 | ]); 117 | }; -------------------------------------------------------------------------------- /src/ExtensionGallery/Code/FeedWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Xml; 4 | 5 | namespace ExtensionGallery.Code 6 | { 7 | public class FeedWriter 8 | { 9 | public string GetFeed(string baseUrl, params Package[] packages) 10 | { 11 | StringBuilder sb = new StringBuilder(); 12 | XmlWriterSettings settings = new XmlWriterSettings 13 | { 14 | Indent = true 15 | }; 16 | 17 | using (XmlWriter writer = XmlWriter.Create(sb, settings)) 18 | { 19 | writer.WriteStartElement("feed", "http://www.w3.org/2005/Atom"); 20 | 21 | writer.WriteElementString("title", "VSIX Gallery"); 22 | writer.WriteElementString("id", "5a7c2525-ddd8-4c44-b2e3-f57ba01a0d81"); 23 | writer.WriteElementString("updated", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ")); 24 | writer.WriteElementString("subtitle", "Add this feed to Visual Studio's extension manager from Tools -> Options -> Environment -> Extensions and Updates"); 25 | 26 | writer.WriteStartElement("link"); 27 | writer.WriteAttributeString("rel", "alternate"); 28 | writer.WriteAttributeString("href", baseUrl); 29 | writer.WriteEndElement(); // link 30 | 31 | foreach (Package package in packages) 32 | { 33 | AddEntry(writer, package, baseUrl); 34 | } 35 | 36 | writer.WriteEndElement(); // feed 37 | } 38 | 39 | return sb.ToString().Replace("utf-16", "utf-8"); 40 | } 41 | 42 | private void AddEntry(XmlWriter writer, Package package, string baseUrl) 43 | { 44 | writer.WriteStartElement("entry"); 45 | 46 | writer.WriteElementString("id", package.ID); 47 | 48 | writer.WriteStartElement("title"); 49 | writer.WriteAttributeString("type", "text"); 50 | writer.WriteValue(package.Name); 51 | writer.WriteEndElement(); // title 52 | 53 | writer.WriteStartElement("link"); 54 | writer.WriteAttributeString("rel", "alternate"); 55 | writer.WriteAttributeString("href", baseUrl + "/extension/" + package.ID); 56 | writer.WriteEndElement(); // link 57 | 58 | writer.WriteStartElement("summary"); 59 | writer.WriteAttributeString("type", "text"); 60 | writer.WriteValue(package.Description); 61 | writer.WriteEndElement(); // summary 62 | 63 | writer.WriteElementString("published", package.DatePublished.ToString("yyyy-MM-ddTHH:mm:ssZ")); 64 | writer.WriteElementString("updated", package.DatePublished.ToString("yyyy-MM-ddTHH:mm:ssZ")); 65 | 66 | writer.WriteStartElement("author"); 67 | writer.WriteElementString("name", package.Author); 68 | writer.WriteEndElement(); // author 69 | 70 | writer.WriteStartElement("content"); 71 | writer.WriteAttributeString("type", "application/octet-stream"); 72 | writer.WriteAttributeString("src", baseUrl + "/extensions/" + package.ID + "/extension.vsix"); 73 | writer.WriteEndElement(); // content 74 | 75 | writer.WriteStartElement("link"); 76 | writer.WriteAttributeString("rel", "icon"); 77 | writer.WriteAttributeString("href", baseUrl + "/extensions/" + package.ID + "/" + package.Icon); 78 | writer.WriteEndElement(); // icon 79 | 80 | writer.WriteStartElement("link"); 81 | writer.WriteAttributeString("rel", "previewimage"); 82 | writer.WriteAttributeString("href", baseUrl + "/extensions/" + package.ID + "/" + package.Preview); 83 | writer.WriteEndElement(); // preview 84 | 85 | writer.WriteRaw("\r\n\r\n"); 86 | 87 | writer.WriteElementString("Id", package.ID); 88 | writer.WriteElementString("Version", package.Version); 89 | 90 | writer.WriteStartElement("References"); 91 | writer.WriteEndElement(); 92 | 93 | writer.WriteRaw("");// Vsix 94 | writer.WriteEndElement(); // entry 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/less/site.less: -------------------------------------------------------------------------------- 1 | @footer-height: 155px; 2 | @min-width: 620px; 3 | 4 | html, body { 5 | height: 100%; 6 | } 7 | 8 | body { 9 | padding-top: 60px; 10 | overflow-y: scroll; 11 | } 12 | 13 | a.btn:hover, a.btn:active, a.btn:focus, 14 | a.btn-sm:hover, a.btn-sm:active, a.btn-sm:focus { 15 | text-decoration: none; 16 | } 17 | 18 | .navbar-brand { 19 | background: url(/img/logo.png) no-repeat 14px center; 20 | padding-left: 40px; 21 | } 22 | 23 | .container { 24 | max-width: 960px; 25 | min-height: 100%; 26 | overflow: auto; 27 | } 28 | 29 | main { 30 | overflow: auto; 31 | margin-bottom: 3em; 32 | padding-bottom: @footer-height; 33 | } 34 | 35 | a { 36 | color: #2f70a9; 37 | 38 | &:focus { 39 | outline: thin; 40 | } 41 | } 42 | 43 | h2 { 44 | font-size: 1.6em; 45 | font-weight: bold; 46 | } 47 | 48 | h3 { 49 | font-size: 1.6em; 50 | margin: 0; 51 | padding: 0; 52 | } 53 | 54 | dd { 55 | padding: 0 0 1em 1em; 56 | } 57 | 58 | img { 59 | max-width: 100%; 60 | } 61 | 62 | #extensions { 63 | position: relative; 64 | 65 | h3 span { 66 | font-size: .6em; 67 | } 68 | 69 | #searchform { 70 | width: 250px; 71 | float: right; 72 | } 73 | 74 | article { 75 | clear: both; 76 | padding: 10px; 77 | margin-top: 3em; 78 | 79 | > div { 80 | margin-left: 120px; 81 | } 82 | 83 | .author { 84 | margin: 3px 0; 85 | display: inline-block; 86 | } 87 | 88 | .icon { 89 | float: left; 90 | } 91 | 92 | .preview { 93 | float: left; 94 | width: 175px; 95 | margin-bottom: 2em; 96 | } 97 | } 98 | 99 | .extension { 100 | margin-top: 0; 101 | 102 | ul { 103 | list-style: none; 104 | padding: 0; 105 | margin: 0 0 1em 0; 106 | 107 | li { 108 | display: inline-block; 109 | margin-right: 1em; 110 | } 111 | } 112 | 113 | .properties { 114 | margin-left: 200px !important; 115 | 116 | .details { 117 | line-height: 1.3; 118 | margin: 0 0 15px 0; 119 | display: inline-block; 120 | 121 | p:first-child { 122 | margin-bottom: 2em; 123 | } 124 | 125 | > p > strong { 126 | display: inline-block; 127 | width: 80px; 128 | } 129 | } 130 | } 131 | } 132 | 133 | .readme{ 134 | line-height: 1.5; 135 | p{ 136 | margin: 1.2em 0; 137 | } 138 | } 139 | } 140 | 141 | #upload { 142 | max-width: 450px; 143 | padding-left: 5px; 144 | 145 | input[type=file] { 146 | width: 100%; 147 | } 148 | 149 | a[ng-click] { 150 | margin-top: 1em; 151 | display: inline-block; 152 | cursor: pointer; 153 | text-decoration: none; 154 | } 155 | 156 | .has-error { 157 | color: red !important; 158 | } 159 | } 160 | 161 | footer { 162 | position: relative; 163 | margin-top: -@footer-height; 164 | height: @footer-height; 165 | clear: both; 166 | border-top: 1px solid lightgray; 167 | background: #f1f1f1; 168 | 169 | ul { 170 | margin-top: 1em; 171 | list-style: none; 172 | padding: 0 6em 0 0; 173 | } 174 | 175 | .copyright { 176 | text-align: right; 177 | font-size: .9em; 178 | color: #808080; 179 | clear: both; 180 | } 181 | } 182 | 183 | @media all and (min-width: 400px) and (max-width: 800px) { 184 | body { 185 | min-width: @min-width; 186 | } 187 | 188 | #extensions article p { 189 | -ms-text-size-adjust: 130%; 190 | } 191 | 192 | .navbar-nav { 193 | display: none; 194 | } 195 | 196 | @-ms-viewport { 197 | width: @min-width; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Properties/PublishProfiles/vsixgallery - Web Deploy-publish.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding(SupportsShouldProcess=$true)] 2 | param($publishProperties, $packOutput, $nugetUrl) 3 | 4 | # to learn more about this file visit http://go.microsoft.com/fwlink/?LinkId=524327 5 | $publishModuleVersion = '1.0.1' 6 | function Get-VisualStudio2015InstallPath{ 7 | [cmdletbinding()] 8 | param() 9 | process{ 10 | $keysToCheck = @('hklm:\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0', 11 | 'hklm:\SOFTWARE\Microsoft\VisualStudio\14.0', 12 | 'hklm:\SOFTWARE\Wow6432Node\Microsoft\VWDExpress\14.0', 13 | 'hklm:\SOFTWARE\Microsoft\VWDExpress\14.0' 14 | ) 15 | [string]$vsInstallPath=$null 16 | 17 | foreach($keyToCheck in $keysToCheck){ 18 | if(Test-Path $keyToCheck){ 19 | $vsInstallPath = (Get-itemproperty $keyToCheck -Name InstallDir -ErrorAction SilentlyContinue | select -ExpandProperty InstallDir -ErrorAction SilentlyContinue) 20 | } 21 | 22 | if($vsInstallPath){ 23 | break; 24 | } 25 | } 26 | 27 | $vsInstallPath 28 | } 29 | } 30 | 31 | $vsInstallPath = Get-VisualStudio2015InstallPath 32 | $publishModulePath = "{0}Extensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath,'1.0.1' 33 | 34 | if(!(Test-Path $publishModulePath)){ 35 | $publishModulePath = "{0}VWDExpressExtensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath,'1.0.1' 36 | } 37 | 38 | $defaultPublishSettings = New-Object psobject -Property @{ 39 | LocalInstallDir = $publishModulePath 40 | } 41 | 42 | function Enable-PackageDownloader{ 43 | [cmdletbinding()] 44 | param( 45 | $toolsDir = "$env:LOCALAPPDATA\Microsoft\Web Tools\Publish\package-downloader-$publishModuleVersion\", 46 | $pkgDownloaderDownloadUrl = 'http://go.microsoft.com/fwlink/?LinkId=524325') # package-downloader.psm1 47 | process{ 48 | if(get-module package-downloader){ 49 | remove-module package-downloader | Out-Null 50 | } 51 | 52 | if(!(get-module package-downloader)){ 53 | if(!(Test-Path $toolsDir)){ New-Item -Path $toolsDir -ItemType Directory -WhatIf:$false } 54 | 55 | $expectedPath = (Join-Path ($toolsDir) 'package-downloader.psm1') 56 | if(!(Test-Path $expectedPath)){ 57 | 'Downloading [{0}] to [{1}]' -f $pkgDownloaderDownloadUrl,$expectedPath | Write-Verbose 58 | (New-Object System.Net.WebClient).DownloadFile($pkgDownloaderDownloadUrl, $expectedPath) 59 | } 60 | 61 | if(!$expectedPath){throw ('Unable to download package-downloader.psm1')} 62 | 63 | 'importing module [{0}]' -f $expectedPath | Write-Output 64 | Import-Module $expectedPath -DisableNameChecking -Force 65 | } 66 | } 67 | } 68 | 69 | function Enable-PublishModule{ 70 | [cmdletbinding()] 71 | param() 72 | process{ 73 | if(get-module publish-module){ 74 | remove-module publish-module | Out-Null 75 | } 76 | 77 | if(!(get-module publish-module)){ 78 | $localpublishmodulepath = Join-Path $defaultPublishSettings.LocalInstallDir 'publish-module.psm1' 79 | if(Test-Path $localpublishmodulepath){ 80 | 'importing module [publish-module="{0}"] from local install dir' -f $localpublishmodulepath | Write-Verbose 81 | Import-Module $localpublishmodulepath -DisableNameChecking -Force 82 | $true 83 | } 84 | } 85 | } 86 | } 87 | 88 | try{ 89 | 90 | if (!(Enable-PublishModule)){ 91 | Enable-PackageDownloader 92 | Enable-NuGetModule -name 'publish-module' -version $publishModuleVersion -nugetUrl $nugetUrl 93 | } 94 | 95 | 'Calling Publish-AspNet' | Write-Verbose 96 | # call Publish-AspNet to perform the publish operation 97 | Publish-AspNet -publishProperties $publishProperties -packOutput $packOutput 98 | } 99 | catch{ 100 | "An error occurred during publish.`n{0}" -f $_.Exception.Message | Write-Error 101 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | bower_components 10 | node_modules 11 | **/wwwroot/extensions 12 | **/wwwroot/lib 13 | **/wwwroot/bin 14 | .vs/ 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | build/ 24 | bld/ 25 | [Oo]bj/ 26 | 27 | # Roslyn cache directories 28 | *.ide/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | #NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | *_i.c 44 | *_p.c 45 | *_i.h 46 | *.ilk 47 | *.meta 48 | *.obj 49 | *.pch 50 | *.pdb 51 | *.pgc 52 | *.pgd 53 | *.rsp 54 | *.sbr 55 | *.tlb 56 | *.tli 57 | *.tlh 58 | *.tmp 59 | *.tmp_proj 60 | *.log 61 | *.vspscc 62 | *.vssscc 63 | .builds 64 | *.pidb 65 | *.svclog 66 | *.scc 67 | 68 | # Chutzpah Test files 69 | _Chutzpah* 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | *.cachefile 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | *.vspx 83 | 84 | # TFS 2012 Local Workspace 85 | $tf/ 86 | 87 | # Guidance Automation Toolkit 88 | *.gpState 89 | 90 | # ReSharper is a .NET coding add-in 91 | _ReSharper*/ 92 | *.[Rr]e[Ss]harper 93 | *.DotSettings.user 94 | 95 | # JustCode is a .NET coding addin-in 96 | .JustCode 97 | 98 | # TeamCity is a build add-in 99 | _TeamCity* 100 | 101 | # DotCover is a Code Coverage Tool 102 | *.dotCover 103 | 104 | # NCrunch 105 | _NCrunch_* 106 | .*crunch*.local.xml 107 | 108 | # MightyMoose 109 | *.mm.* 110 | AutoTest.Net/ 111 | 112 | # Web workbench (sass) 113 | .sass-cache/ 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.[Pp]ublish.xml 133 | *.azurePubxml 134 | # TODO: Comment the next line if you want to checkin your web deploy settings 135 | # but database connection strings (with potential passwords) will be unencrypted 136 | *.pubxml 137 | *.publishproj 138 | 139 | # NuGet Packages 140 | *.nupkg 141 | # The packages folder can be ignored because of Package Restore 142 | **/packages/* 143 | # except build/, which is used as an MSBuild target. 144 | !**/packages/build/ 145 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 146 | #!**/packages/repositories.config 147 | 148 | # Windows Azure Build Output 149 | csx/ 150 | *.build.csdef 151 | 152 | # Windows Store app package directory 153 | AppPackages/ 154 | 155 | # Others 156 | sql/ 157 | *.Cache 158 | ClientBin/ 159 | [Ss]tyle[Cc]op.* 160 | ~$* 161 | *~ 162 | *.dbmdl 163 | *.dbproj.schemaview 164 | *.pfx 165 | *.publishsettings 166 | node_modules/ 167 | 168 | # RIA/Silverlight projects 169 | Generated_Code/ 170 | 171 | # Backup & report files from converting an old project file 172 | # to a newer Visual Studio version. Backup files are not needed, 173 | # because we have git ;-) 174 | _UpgradeReport_Files/ 175 | Backup*/ 176 | UpgradeLog*.XML 177 | UpgradeLog*.htm 178 | 179 | # SQL Server files 180 | *.mdf 181 | *.ldf 182 | 183 | # Business Intelligence projects 184 | *.rdl.data 185 | *.bim.layout 186 | *.bim_*.settings 187 | 188 | # Microsoft Fakes 189 | FakesAssemblies/ 190 | 191 | # ========================= 192 | # Operating System Files 193 | # ========================= 194 | 195 | # OSX 196 | # ========================= 197 | 198 | .DS_Store 199 | .AppleDouble 200 | .LSOverride 201 | 202 | # Thumbnails 203 | ._* 204 | 205 | # Files that might appear on external disk 206 | .Spotlight-V100 207 | .Trashes 208 | 209 | # Directories potentially created on remote AFP share 210 | .AppleDB 211 | .AppleDesktop 212 | Network Trash Folder 213 | Temporary Items 214 | .apdisk 215 | 216 | # Windows 217 | # ========================= 218 | 219 | # Windows image file caches 220 | Thumbs.db 221 | ehthumbs.db 222 | 223 | # Folder config file 224 | Desktop.ini 225 | 226 | # Recycle Bin used on file shares 227 | $RECYCLE.BIN/ 228 | 229 | # Windows Installer files 230 | *.cab 231 | *.msi 232 | *.msm 233 | *.msp 234 | 235 | # Windows shortcuts 236 | *.lnk 237 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNet.Http; 3 | using System.Linq; 4 | using Microsoft.AspNet.Builder; 5 | using Microsoft.AspNet.Diagnostics; 6 | using Microsoft.AspNet.Hosting; 7 | using Microsoft.AspNet.Routing; 8 | using Microsoft.AspNet.StaticFiles; 9 | using Microsoft.Framework.Configuration; 10 | using Microsoft.Framework.DependencyInjection; 11 | using Microsoft.Framework.Logging; 12 | using Microsoft.Framework.Runtime; 13 | 14 | namespace ExtensionGallery 15 | { 16 | public class Startup 17 | { 18 | public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv) 19 | { 20 | // Setup configuration sources. 21 | Configuration = new ConfigurationBuilder(appEnv.ApplicationBasePath) 22 | .AddJsonFile("config.json") 23 | .AddEnvironmentVariables().Build(); 24 | } 25 | 26 | public IConfiguration Configuration { get; set; } 27 | 28 | // This method gets called by the runtime. 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | // Add EF services to the services container. 32 | //services.AddEntityFramework(Configuration) 33 | // .AddSqlServer() 34 | // .AddDbContext(); 35 | 36 | //// Add Identity services to the services container. 37 | //services.AddDefaultIdentity(Configuration); 38 | 39 | // Add MVC services to the services container. 40 | services.AddMvc(); 41 | 42 | // Uncomment the following line to add Web API servcies which makes it easier to port Web API 2 controllers. 43 | // You need to add Microsoft.AspNet.Mvc.WebApiCompatShim package to project.json 44 | // services.AddWebApiConventions(); 45 | 46 | } 47 | 48 | // Configure is called after ConfigureServices is called. 49 | public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) 50 | { 51 | // Configure the HTTP request pipeline. 52 | // Add the console logger. 53 | //loggerfactory.AddConsole(); 54 | 55 | // Add the following to the request pipeline only in development environment. 56 | if (string.Equals(env.EnvironmentName, "Development", StringComparison.OrdinalIgnoreCase)) 57 | { 58 | //app.UseBrowserLink(); 59 | app.UseErrorPage(ErrorPageOptions.ShowAll); 60 | //app.UseDatabaseErrorPage(DatabaseErrorPageOptions.ShowAll); 61 | } 62 | else 63 | { 64 | // Add Error handling middleware which catches all application specific errors and 65 | // send the request to the following path or controller action. 66 | //app.UseErrorHandler("/Home/Error"); 67 | } 68 | 69 | // app.UseErrorPage(ErrorPageOptions.ShowAll); 70 | 71 | // Add static files to the request pipeline. 72 | //var options = new StaticFileOptions(); 73 | //options.ContentTypeProvider = new CustomContentTypeProvider(); 74 | //options.ServeUnknownFileTypes = true; 75 | //options.OnPrepareResponse = _ => 76 | //{ 77 | // string[] valid = new[] { ".js", ".css", ".ico", ".png", ".gif", ".jpg", ".svg", ".woff", ".woff2", ".ttf", ".eot", ".vsix", ".map" }; 78 | // string ext = System.IO.Path.GetExtension(_.File.Name).ToLowerInvariant(); 79 | // if (valid.Contains(ext)) 80 | // { 81 | // _.Context.Response.Headers["cache-control"] = "public, max-age=31536000"; 82 | // } 83 | //}; 84 | //app.UseStaticFiles(options); 85 | 86 | // Add cookie-based authentication to the request pipeline. 87 | //app.UseIdentity(); 88 | 89 | // Add MVC to the request pipeline. 90 | app.UseMvc(routes => 91 | { 92 | //routes.MapRoute( 93 | // name: "extension", 94 | // template: "extension/{id}", 95 | // defaults: new { controller = "Api", action = "Html" }); 96 | 97 | //routes.MapRoute( 98 | // name: "author", 99 | // template: "author/{name}", 100 | // defaults: new { controller = "Api", action = "Html" }); 101 | 102 | //routes.MapRoute( 103 | // name: "upload", 104 | // template: "upload", 105 | // defaults: new { controller = "Api", action = "Html" }); 106 | 107 | routes.MapRoute( 108 | name: "default", 109 | template: "{controller}/{action}/{id?}", 110 | defaults: new { controller = "Home", action = "Index" }); 111 | }); 112 | } 113 | } 114 | 115 | public class CustomContentTypeProvider : FileExtensionContentTypeProvider 116 | { 117 | public CustomContentTypeProvider() 118 | { 119 | Mappings[".html"] = "text/html;charset=utf-8"; 120 | Mappings[".js"] = "text/javascript"; 121 | Mappings[".ico"] = "image/x-icon"; 122 | Mappings[".vsix"] = "application/octed-stream"; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/ExtensionGallery/Code/PackageHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Newtonsoft.Json; 9 | 10 | namespace ExtensionGallery.Code 11 | { 12 | public class PackageHelper 13 | { 14 | private string _webroot; 15 | private string _extensionRoot; 16 | public static List _cache; 17 | 18 | public PackageHelper(string webroot) 19 | { 20 | _webroot = webroot; 21 | _extensionRoot = Path.Combine(webroot, "extensions"); 22 | } 23 | 24 | public List PackageCache 25 | { 26 | get 27 | { 28 | if (_cache == null) 29 | _cache = GetAllPackages(); 30 | 31 | return _cache; 32 | } 33 | } 34 | 35 | private List GetAllPackages() 36 | { 37 | List packages = new List(); 38 | 39 | if (!Directory.Exists(_extensionRoot)) 40 | { 41 | return packages.ToList(); 42 | } 43 | 44 | foreach (string extension in Directory.EnumerateDirectories(_extensionRoot)) 45 | { 46 | string json = Path.Combine(extension, "extension.json"); 47 | if (File.Exists(json)) 48 | { 49 | string content = File.ReadAllText(json); 50 | Package package = JsonConvert.DeserializeObject(content, typeof(Package)) as Package; 51 | packages.Add(package); 52 | } 53 | } 54 | 55 | return packages.OrderByDescending(p => p.DatePublished).ToList(); 56 | } 57 | 58 | public Package GetPackage(string id) 59 | { 60 | if (PackageCache.Any(p => p.ID == id)) 61 | { 62 | return PackageCache.SingleOrDefault(p => p.ID == id); 63 | } 64 | 65 | string folder = Path.Combine(_extensionRoot, id); 66 | List packages = new List(); 67 | 68 | return DeserializePackage(folder); 69 | } 70 | 71 | private static Package DeserializePackage(string version) 72 | { 73 | string content = File.ReadAllText(Path.Combine(version, "extension.json")); 74 | return JsonConvert.DeserializeObject(content, typeof(Package)) as Package; 75 | } 76 | 77 | public async Task ProcessVsix(Stream vsixStream, string repo, string issuetracker) 78 | { 79 | string tempFolder = Path.Combine(_webroot, "temp", Guid.NewGuid().ToString()); 80 | 81 | try 82 | { 83 | string tempVsix = Path.Combine(tempFolder, "extension.vsix"); 84 | 85 | if (!Directory.Exists(tempFolder)) 86 | Directory.CreateDirectory(tempFolder); 87 | 88 | using (FileStream fileStream = File.Create(tempVsix)) 89 | { 90 | await vsixStream.CopyToAsync(fileStream); 91 | } 92 | 93 | ZipFile.ExtractToDirectory(tempVsix, tempFolder); 94 | 95 | VsixManifestParser parser = new VsixManifestParser(); 96 | Package package = parser.CreateFromManifest(tempFolder, repo, issuetracker); 97 | 98 | //if (PackageCache.Any(p => p.ID == package.ID && new Version(p.Version) > new Version(package.Version))) 99 | // throw new ArgumentException("The VSIX version (" + package.Version + ") must be equal or higher than the existing VSIX"); 100 | 101 | string vsixFolder = Path.Combine(_extensionRoot, package.ID); 102 | 103 | SavePackage(tempFolder, package, vsixFolder); 104 | 105 | File.Copy(tempVsix, Path.Combine(vsixFolder, "extension.vsix"), true); 106 | 107 | return package; 108 | } 109 | finally 110 | { 111 | Directory.Delete(tempFolder, true); 112 | } 113 | } 114 | 115 | private void SavePackage(string tempFolder, Package package, string vsixFolder) 116 | { 117 | if (Directory.Exists(vsixFolder)) 118 | Directory.Delete(vsixFolder, true); 119 | 120 | Directory.CreateDirectory(vsixFolder); 121 | 122 | string icon = Path.Combine(tempFolder, package.Icon ?? string.Empty); 123 | if (File.Exists(icon)) 124 | { 125 | File.Copy(icon, Path.Combine(vsixFolder, "icon-" + package.Version + ".png"), true); 126 | package.Icon = "icon-" + package.Version + ".png"; 127 | } 128 | 129 | string preview = Path.Combine(tempFolder, package.Preview ?? string.Empty); 130 | if (File.Exists(preview)) 131 | { 132 | File.Copy(preview, Path.Combine(vsixFolder, "preview-" + package.Version + ".png"), true); 133 | package.Preview = "preview-" + package.Version + ".png"; 134 | } 135 | 136 | string json = JsonConvert.SerializeObject(package); 137 | 138 | File.WriteAllText(Path.Combine(vsixFolder, "extension.json"), json, Encoding.UTF8); 139 | 140 | Package existing = PackageCache.FirstOrDefault(p => p.ID == package.ID); 141 | 142 | if (PackageCache.Contains(existing)) 143 | { 144 | PackageCache.Remove(existing); 145 | } 146 | 147 | PackageCache.Add(package); 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/Code/VsixManifestParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text.RegularExpressions; 5 | using System.Xml; 6 | 7 | namespace ExtensionGallery.Code 8 | { 9 | public class VsixManifestParser 10 | { 11 | public Package CreateFromManifest(string tempFolder, string repo, string issuetracker) 12 | { 13 | string xml = File.ReadAllText(Path.Combine(tempFolder, "extension.vsixmanifest")); 14 | xml = Regex.Replace(xml, "( xmlns(:\\w+)?)=\"([^\"]+)\"", string.Empty); 15 | 16 | XmlDocument doc = new XmlDocument(); 17 | doc.LoadXml(xml); 18 | 19 | Package package = new Package(); 20 | 21 | if (doc.GetElementsByTagName("DisplayName").Count > 0) 22 | { 23 | Vs2012Format(repo, issuetracker, doc, package); 24 | } 25 | else 26 | { 27 | Vs2010Format(repo, issuetracker, doc, package); 28 | } 29 | 30 | string license = ParseNode(doc, "License", false); 31 | if (!string.IsNullOrEmpty(license)) 32 | { 33 | string path = Path.Combine(tempFolder, license); 34 | if (File.Exists(path)) 35 | { 36 | package.License = File.ReadAllText(path); 37 | } 38 | } 39 | 40 | return package; 41 | } 42 | 43 | private void Vs2012Format(string repo, string issuetracker, XmlDocument doc, Package package) 44 | { 45 | package.ID = ParseNode(doc, "Identity", true, "Id"); 46 | package.Name = ParseNode(doc, "DisplayName", true); 47 | package.Description = ParseNode(doc, "Description", true); 48 | package.Version = new Version(ParseNode(doc, "Identity", true, "Version")).ToString(); 49 | package.Author = ParseNode(doc, "Identity", true, "Publisher"); 50 | package.Icon = ParseNode(doc, "Icon", false); 51 | package.Preview = ParseNode(doc, "PreviewImage", false); 52 | package.Tags = ParseNode(doc, "Tags", false); 53 | package.DatePublished = DateTime.UtcNow; 54 | package.SupportedVersions = GetSupportedVersions(doc); 55 | package.ReleaseNotesUrl = ParseNode(doc, "ReleaseNotes", false); 56 | package.GettingStartedUrl = ParseNode(doc, "GettingStartedGuide", false); 57 | package.MoreInfoUrl = ParseNode(doc, "MoreInfo", false); 58 | package.Repo = repo; 59 | package.IssueTracker = issuetracker; 60 | } 61 | 62 | private void Vs2010Format(string repo, string issuetracker, XmlDocument doc, Package package) 63 | { 64 | package.ID = ParseNode(doc, "Identifier", true, "Id"); 65 | package.Name = ParseNode(doc, "Name", true); 66 | package.Description = ParseNode(doc, "Description", true); 67 | package.Version = new Version(ParseNode(doc, "Version", true)).ToString(); 68 | package.Author = ParseNode(doc, "Author", true); 69 | package.Icon = ParseNode(doc, "Icon", false); 70 | package.Preview = ParseNode(doc, "PreviewImage", false); 71 | package.DatePublished = DateTime.UtcNow; 72 | package.SupportedVersions = GetSupportedVersions(doc); 73 | package.ReleaseNotesUrl = ParseNode(doc, "ReleaseNotes", false); 74 | package.GettingStartedUrl = ParseNode(doc, "GettingStartedGuide", false); 75 | package.MoreInfoUrl = ParseNode(doc, "MoreInfo", false); 76 | package.Repo = repo; 77 | package.IssueTracker = issuetracker; 78 | } 79 | 80 | private static IEnumerable GetSupportedVersions(XmlDocument doc) 81 | { 82 | XmlNodeList list = doc.GetElementsByTagName("InstallationTarget"); 83 | 84 | if (list.Count == 0) 85 | list = doc.GetElementsByTagName(" versions = new List(); 88 | 89 | foreach (XmlNode node in list) 90 | { 91 | string raw = node.Attributes["Version"].Value.Trim('[', '(', ']', ')'); 92 | string[] entries = raw.Split(','); 93 | 94 | foreach (string entry in entries) 95 | { 96 | Version v; 97 | if (Version.TryParse(entry, out v) && !versions.Contains(v.ToString())) 98 | { 99 | versions.Add(v.ToString()); 100 | } 101 | } 102 | } 103 | 104 | return versions; 105 | } 106 | 107 | private string ParseNode(XmlDocument doc, string name, bool required, string attribute = "") 108 | { 109 | XmlNodeList list = doc.GetElementsByTagName(name); 110 | 111 | if (list.Count > 0) 112 | { 113 | XmlNode node = list[0]; 114 | 115 | if (string.IsNullOrEmpty(attribute)) 116 | return node.InnerText; 117 | 118 | XmlAttribute attr = node.Attributes[attribute]; 119 | 120 | if (attr != null) 121 | return attr.Value; 122 | } 123 | 124 | if (required) 125 | { 126 | string message = string.Format("Attribute '{0}' could not be found on the '{1}' element in the .vsixmanifest file.", attribute, name); 127 | throw new Exception(message); 128 | } 129 | 130 | return null; 131 | } 132 | 133 | } 134 | } -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Visual Studio Extension Gallery 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 62 | 63 |
64 |
65 |
66 |
67 |
68 | 69 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Visual Studio Extension Gallery 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 64 | 65 |
66 |
67 |
68 |
69 |
70 | 71 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/ExtensionGallery/app/views/templates.js: -------------------------------------------------------------------------------- 1 | angular.module('galleryApp').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('app/views/devguide.html', 5 | "

Add your extension

You can add your extension to this gallery in 2 different ways as part of your build automation.

  1. Use PowerShell
  2. Use AppVeyor

Both PowerShell and AppVeyor uses a custom script that makes it easy to publish the extension to this gallery. It contains other functions that are useful for incrementing the VSIX version and other handy things.

Use PowerShell

First you must execute the VSIX script

(new-object Net.WebClient).DownloadString(\"https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1\") | iex

That allows you to call methods upload the .vsix extension file to the gallery.

Vsix-PublishToGallery

That will find all .vsix files in the working directory recursively and upload them. To specify the path, simply pass it in as the first parameter:

Vsix-PublishToGallery .\\src\\WebCompilerVsix\\**\\*.vsix

Use AppVeyor

AppVeyor is a build server hosted in the cloud and it's free.

After you've created an account, you can start doing automated builds. A really nice thing is that AppVeyor can automatically kick off a new build when you commit code to either GitHub, VSO or other code repositories.

To automatically upload your extension to vsixgallery.com when the build has succeeded, all you have to do is to add an appveyor.yml file to the root of your repository. The content of the file should look like this:

version: 1.0.{build}\r" +
 6 |     "\n" +
 7 |     "\r" +
 8 |     "\n" +
 9 |     "install:\r" +
10 |     "\n" +
11 |     "  - ps: (new-object Net.WebClient).DownloadString(\"https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1\") | iex\r" +
12 |     "\n" +
13 |     "\r" +
14 |     "\n" +
15 |     "before_build:\r" +
16 |     "\n" +
17 |     "  - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion\r" +
18 |     "\n" +
19 |     "\r" +
20 |     "\n" +
21 |     "build_script:\r" +
22 |     "\n" +
23 |     "  - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m\r" +
24 |     "\n" +
25 |     "\r" +
26 |     "\n" +
27 |     "after_test:\r" +
28 |     "\n" +
29 |     "  - ps: Vsix-PushArtifacts | Vsix-PublishToGallery\r" +
30 |     "\n" +
31 |     "

You might want to check out these real-world uses:

" 32 | ); 33 | 34 | 35 | $templateCache.put('app/views/extension.html', 36 | "

{{package.Name}}


License

" 37 | ); 38 | 39 | 40 | $templateCache.put('app/views/feedguide.html', 41 | "

Subscribe to feed

Visual Studio is capable of subscribing to extension feeds, so you will be notified of any updates to extensions found in this gallery. You will only be notified of any updates to extensions that you already have installed.

Choose the right feed

There are several feeds in this gallery you can subscribe to.

The main feed
By subscribing to the main feed you will be notified by Visual Studio whenever any of the extensions in this gallery are updated. The feed contains all extensions available.
Feed per author
Each extension author has their own feed, so you can choose to subsribe to one or more authors' extensions.
Feed for individual extension
If you're only interested in getting updates to specific extensions, then you can subsribe to feeds for that one extension.

Setup

In Visual Studio go to Tools -> Options -> Environment -> Extensions and Updates.

\"Visual

Click the Add button and fill in the name and URL fields.

Name: Give it a name you like

URL: Could be the main feed http://vsixgallery.com/feed/

And finally click the Apply button.

That's it. You've now added the nightly feed to Visual Studio and updates will start to show up in Tools -> Extensions and Updates dialog


Extensions and Updates

You can now see the updates coming in to the Tools -> Extensions and Updates dialog.

\"Visual

" 42 | ); 43 | 44 | 45 | $templateCache.put('app/views/home.html', 46 | "

{{pageTitle}} Feed

No extensions found

" 47 | ); 48 | 49 | 50 | $templateCache.put('app/views/upload.html', 51 | "

Upload new extension

Upload a new extension to the gallery. It can either be a brand new extension or and updated version of an extension that's already on the gallery.

Only .vsix files are supported

{{error}}

" 52 | ); 53 | 54 | }]); 55 | -------------------------------------------------------------------------------- /src/ExtensionGallery/wwwroot/js/optimized.0334f283.js: -------------------------------------------------------------------------------- 1 | var galleryApp=angular.module("galleryApp",["ngRoute"]).filter("escape",function(){return window.encodeURIComponent}).filter("rawHtml",["$sce",function(a){return function(b){return a.trustAsHtml(b)}}]).config(["$routeProvider","$locationProvider",function(a,b){b.html5Mode(!0),a.when("/",{title:"Visual Studio Extension Gallery",controller:"homeController",templateUrl:"app/views/home.html"}).when("/author/:name",{controller:"authorController",templateUrl:"app/views/home.html"}).when("/extension/:id/",{controller:"extensionController",templateUrl:"app/views/extension.html"}).when("/upload",{controller:"uploadController",templateUrl:"app/views/upload.html"}).when("/guide/dev/",{controller:"devguideController",templateUrl:"app/views/devguide.html"}).when("/guide/feed/",{controller:"feedguideController",templateUrl:"app/views/feedguide.html"}).otherwise({redirectTo:"/"})}]),constants={DEFAULT_ICON_IMAGE:"/img/default-icon.png",DEFAULT_PREVIEW_IMAGE:"/img/default-preview.png"};galleryApp.service("dataService",["$http",function(a){function b(a){return a.DownloadUrl=d+a.ID+"/"+encodeURIComponent(a.Name+" v"+a.Version)+".vsix",a.Icon?a.Icon=d+a.ID+"/"+a.Icon:a.Icon=constants.DEFAULT_ICON_IMAGE,a.Preview?a.Preview=d+a.ID+"/"+a.Preview:a.Preview=constants.DEFAULT_PREVIEW_IMAGE,a.SupportedVersions=a.SupportedVersions.map(function(a){return 0==a.indexOf("11.")?2012:0==a.indexOf("12.")?2013:0==a.indexOf("14.")?2015:void 0}),a.relativeDate=moment(a.DatePublished).fromNow(),a}var c="/api/",d="/extensions/",e=[];this.getAllExtensions=function(d){return e.length>0?d(e):void a.get(c+"get/").success(function(a){for(var c=0;c0?f(g[0]):void a.get(c+"get/"+d).success(function(a){f(b(a))})},this.upload=function(d,f,g){a.post(c+"upload"+f,new Blob([d],{})).success(function(a){for(var c=0;cAdd your extension

You can add your extension to this gallery in 2 different ways as part of your build automation.

  1. Use PowerShell
  2. Use AppVeyor

Both PowerShell and AppVeyor uses a custom script that makes it easy to publish the extension to this gallery. It contains other functions that are useful for incrementing the VSIX version and other handy things.

Use PowerShell

First you must execute the VSIX script

(new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex

That allows you to call methods upload the .vsix extension file to the gallery.

Vsix-PublishToGallery

That will find all .vsix files in the working directory recursively and upload them. To specify the path, simply pass it in as the first parameter:

Vsix-PublishToGallery .\\src\\WebCompilerVsix\\**\\*.vsix

Use AppVeyor

AppVeyor is a build server hosted in the cloud and it\'s free.

After you\'ve created an account, you can start doing automated builds. A really nice thing is that AppVeyor can automatically kick off a new build when you commit code to either GitHub, VSO or other code repositories.

To automatically upload your extension to vsixgallery.com when the build has succeeded, all you have to do is to add an appveyor.yml file to the root of your repository. The content of the file should look like this:

version: 1.0.{build}\r\n\r\ninstall:\r\n  - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex\r\n\r\nbefore_build:\r\n  - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion\r\n\r\nbuild_script:\r\n  - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m\r\n\r\nafter_test:\r\n  - ps: Vsix-PushArtifacts | Vsix-PublishToGallery\r\n

You might want to check out these real-world uses:

'),a.put("app/views/extension.html",'

{{package.Name}}


License

'),a.put("app/views/feedguide.html",'

Subscribe to feed

Visual Studio is capable of subscribing to extension feeds, so you will be notified of any updates to extensions found in this gallery. You will only be notified of any updates to extensions that you already have installed.

Choose the right feed

There are several feeds in this gallery you can subscribe to.

The main feed
By subscribing to the main feed you will be notified by Visual Studio whenever any of the extensions in this gallery are updated. The feed contains all extensions available.
Feed per author
Each extension author has their own feed, so you can choose to subsribe to one or more authors\' extensions.
Feed for individual extension
If you\'re only interested in getting updates to specific extensions, then you can subsribe to feeds for that one extension.

Setup

In Visual Studio go to Tools -> Options -> Environment -> Extensions and Updates.

Visual Studio options dialog

Click the Add button and fill in the name and URL fields.

Name: Give it a name you like

URL: Could be the main feed http://vsixgallery.com/feed/

And finally click the Apply button.

That\'s it. You\'ve now added the nightly feed to Visual Studio and updates will start to show up in Tools -> Extensions and Updates dialog


Extensions and Updates

You can now see the updates coming in to the Tools -> Extensions and Updates dialog.

Visual Studio Extensions and Updates

'),a.put("app/views/home.html",'

{{pageTitle}} Feed

No extensions found

'),a.put("app/views/upload.html",'

Upload new extension

Upload a new extension to the gallery. It can either be a brand new extension or and updated version of an extension that\'s already on the gallery.

Only .vsix files are supported

{{error}}

')}]),galleryApp.controller("authorController",["$scope","$rootScope","$route","dataService",function(a,b,c,d){function e(a){return a.replace(/\w\S*/g,function(a){return a.charAt(0).toUpperCase()+a.substr(1).toLowerCase()})}b.pageTitle="Extensions by "+e(c.current.params.name),a.feed="/feed/author/"+c.current.params.name+"/",a.query="",a.packageSearch=function(b){var c=a.query.toUpperCase();return-1!=b.Name.toUpperCase().indexOf(c)||-1!=b.Description.toUpperCase().indexOf(c)||-1!=b.Author.toUpperCase().indexOf(c)||b.Tags&&-1!=b.Tags.toUpperCase().indexOf(c)},d.getAllExtensions(function(b){var d=c.current.params.name.toUpperCase();a.packages=b.filter(function(a){return a.Author.toUpperCase()===d})})}]),galleryApp.controller("feedguideController",["$scope","$rootScope",function(a,b){b.pageTitle="Subscribe to feed",window.scrollTo(0,0)}]),galleryApp.controller("devguideController",["$scope","$rootScope",function(a,b){b.pageTitle="Add your extension",window.scrollTo(0,0)}]),angular.bootstrap(document.querySelector("html"),["galleryApp"]); --------------------------------------------------------------------------------