This commit is contained in:
2019-09-17 13:20:42 -04:00
parent d211d1dc34
commit bef10ce4c9
8352 changed files with 568242 additions and 51 deletions
+18
View File
@@ -0,0 +1,18 @@
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+38
View File
@@ -0,0 +1,38 @@
# vsce
> *The Visual Studio Code Extension Manager*
[![Build Status](https://dev.azure.com/vscode/VSCE/_apis/build/status/VSCE?branchName=master)](https://dev.azure.com/vscode/VSCE/_build/latest?definitionId=16&branchName=master) [![npm version](https://badge.fury.io/js/vsce.svg)](https://badge.fury.io/js/vsce)
## Requirements
- [Node.js](https://nodejs.org/en/) at least `8.x.x`
## Development
First clone this repository, then:
```sh
yarn
yarn watch # or `watch-test` to also run tests
```
Once the watcher is up and running, you can run out of sources with:
```sh
yarn vsce
```
### Publish to NPM
Simply push a new tag and the CI will automatically publish to NPM. The usual flow is:
```sh
npm version [minor|patch]
git push --follow-tags
```
## About
This tool assists in packaging and publishing Visual Studio Code extensions.
Read the [**Documentation**](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code website.
+292
View File
@@ -0,0 +1,292 @@
microsoft-vscode-vsce
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
Do Not Translate or Localize
This project incorporates components from the projects listed below. The original copyright notices and the licenses
under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted
herein, whether by implication, estoppel or otherwise.
1. commander version 2.8.1 (https://github.com/tj/commander.js)
2. denodeify version 1.2.1 (https://github.com/matthew-andrews/denodeify)
3. glob version 5.0.14 (https://github.com/isaacs/node-glob)
4. lodash version 3.10.1 (https://github.com/lodash/lodash)
5. mime version 1.3.4 (https://github.com/broofa/node-mime)
6. minimatch version 2.0.10 (https://github.com/isaacs/minimatch)
7. osenv version 0.1.3 (https://github.com/npm/osenv)
8. read version 1.0.7 (https://github.com/isaacs/read)
9. semver version 5.1.0 (https://github.com/npm/node-semver)
10. tmp version 0.0.27 (https://github.com/raszi/node-tmp)
11. url-join version 0.0.1 (https://github.com/jfromaniello/url-join)
12. vso-node-api version 0.5.0 (https://github.com/Microsoft/vso-node-api)
13. yauzl version 2.3.1 (https://github.com/thejoshwolfe/yauzl)
14. yazl version 2.2.2 (https://github.com/thejoshwolfe/yazl)
%% commander NOTICES AND INFORMATION BEGIN HERE
(The MIT License)
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END OF commander NOTICES AND INFORMATION
%% denodeify NOTICES AND INFORMATION BEGIN HERE
Credits and collaboration
The lead developer of denodeify is Matt Andrews at FT Labs with much help and support from Kornel Lesiński. All open source code released by FT Labs is licenced under the MIT licence. We welcome comments, feedback and suggestions. Please feel free to raise an issue or pull request.
END OF denodeify NOTICES AND INFORMATION
%% glob NOTICES AND INFORMATION BEGIN HERE
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF glob NOTICES AND INFORMATION
%% lodash NOTICES AND INFORMATION BEGIN HERE
Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
Based on Underscore.js, copyright 2009-2015 Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
END OF lodash NOTICES AND INFORMATION
%% mime NOTICES AND INFORMATION BEGIN HERE
Copyright (c) 2010 Benjamin Thomas, Robert Kieffer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
END OF mime NOTICES AND INFORMATION
%% minimatch NOTICES AND INFORMATION BEGIN HERE
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF minimatch NOTICES AND INFORMATION
%% osenv NOTICES AND INFORMATION BEGIN HERE
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF osenv NOTICES AND INFORMATION
%% read NOTICES AND INFORMATION BEGIN HERE
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF read NOTICES AND INFORMATION
%% semver NOTICES AND INFORMATION BEGIN HERE
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
END OF semver NOTICES AND INFORMATION
%% tmp NOTICES AND INFORMATION BEGIN HERE
The MIT License (MIT)
Copyright (c) 2014 KARASZI István
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF tmp NOTICES AND INFORMATION
%% url-join NOTICES AND INFORMATION BEGIN HERE
License
MIT
END OF url-join NOTICES AND INFORMATION
%% vso-node-api NOTICES AND INFORMATION BEGIN HERE
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF vso-node-api NOTICES AND INFORMATION
%% yauzl NOTICES AND INFORMATION BEGIN HERE
The MIT License (MIT)
Copyright (c) 2014 Josh Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF yauzl NOTICES AND INFORMATION
%% yazl NOTICES AND INFORMATION BEGIN HERE
The MIT License (MIT)
Copyright (c) 2014 Josh Wolfe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
END OF yazl NOTICES AND INFORMATION
+31
View File
@@ -0,0 +1,31 @@
trigger:
branches:
include: ['*']
tags:
include: ['*']
steps:
- task: NodeTool@0
inputs:
versionSpec: "8.x"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2
inputs:
versionSpec: "1.x"
- script: yarn
displayName: Install Dependencies
- script: yarn compile
displayName: Compile
- script: yarn test
displayName: Run Tests
- task: Npm@1
displayName: 'Publish to NPM'
inputs:
command: publish
verbose: false
publishEndpoint: 'NPM'
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))
+1
View File
@@ -0,0 +1 @@
../../../markdown-it/bin/markdown-it.js
+1
View File
@@ -0,0 +1 @@
../../../mime/cli.js
+1
View File
@@ -0,0 +1 @@
../../../semver/bin/semver
+111
View File
@@ -0,0 +1,111 @@
export interface ICreateVSIXOptions {
/**
* The location of the extension in the file system.
*
* Defaults to `process.cwd()`.
*/
cwd?: string;
/**
* The destination of the packaged the VSIX.
*
* Defaults to `NAME-VERSION.vsix`.
*/
packagePath?: string;
/**
* The base URL for links detected in Markdown files.
*/
baseContentUrl?: string;
/**
* The base URL for images detected in Markdown files.
*/
baseImagesUrl?: string;
/**
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
}
export interface IPublishOptions {
/**
* The location of the extension in the file system.
*
* Defaults to `process.cwd()`.
*/
cwd?: string;
/**
* The Personal Access Token to use.
*
* Defaults to the stored one.
*/
pat?: string;
/**
* The base URL for links detected in Markdown files.
*/
baseContentUrl?: string;
/**
* The base URL for images detected in Markdown files.
*/
baseImagesUrl?: string;
/**
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
}
/**
* The supported list of package managers.
*/
export declare enum PackageManager {
Npm = 0,
Yarn = 1
}
export interface IListFilesOptions {
/**
* The working directory of the extension. Defaults to `process.cwd()`.
*/
cwd?: string;
/**
* The package manager to use. Defaults to `PackageManager.Npm`.
*/
packageManager?: PackageManager;
/**
* A subset of the top level dependencies which should be included. The
* default is `undefined` which include all dependencies, an empty array means
* no dependencies will be included.
*/
packagedDependencies?: string[];
}
export interface IPublishVSIXOptions {
/**
* The Personal Access Token to use.
*
* Defaults to the stored one.
*/
pat?: string;
/**
* The base URL for links detected in Markdown files.
*/
baseContentUrl?: string;
/**
* The base URL for images detected in Markdown files.
*/
baseImagesUrl?: string;
/**
* Should use Yarn instead of NPM.
*/
useYarn?: boolean;
}
/**
* Creates a VSIX from the extension in the current working directory.
*/
export declare function createVSIX(options?: ICreateVSIXOptions): Promise<any>;
/**
* Publishes the extension in the current working directory.
*/
export declare function publish(options?: IPublishOptions): Promise<any>;
/**
* Lists the files included in the extension's package.
*/
export declare function listFiles(options?: IListFilesOptions): Promise<string[]>;
/**
* Publishes a pre-build VSIX.
*/
export declare function publishVSIX(packagePath: string, options?: IPublishVSIXOptions): Promise<any>;
+40
View File
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const publish_1 = require("./publish");
const package_1 = require("./package");
/**
* The supported list of package managers.
*/
var PackageManager;
(function (PackageManager) {
PackageManager[PackageManager["Npm"] = 0] = "Npm";
PackageManager[PackageManager["Yarn"] = 1] = "Yarn";
})(PackageManager = exports.PackageManager || (exports.PackageManager = {}));
/**
* Creates a VSIX from the extension in the current working directory.
*/
function createVSIX(options = {}) {
return package_1.packageCommand(options);
}
exports.createVSIX = createVSIX;
/**
* Publishes the extension in the current working directory.
*/
function publish(options = {}) {
return publish_1.publish(options);
}
exports.publish = publish;
/**
* Lists the files included in the extension's package.
*/
function listFiles(options = {}) {
return package_1.listFiles(options.cwd, options.packageManager === PackageManager.Yarn, options.packagedDependencies);
}
exports.listFiles = listFiles;
/**
* Publishes a pre-build VSIX.
*/
function publishVSIX(packagePath, options = {}) {
return publish_1.publish(Object.assign({ packagePath }, options));
}
exports.publishVSIX = publishVSIX;
+126
View File
@@ -0,0 +1,126 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const program = require("commander");
const didYouMean = require("didyoumean");
const package_1 = require("./package");
const publish_1 = require("./publish");
const show_1 = require("./show");
const search_1 = require("./search");
const store_1 = require("./store");
const npm_1 = require("./npm");
const util_1 = require("./util");
const semver = require("semver");
const tty_1 = require("tty");
const pkg = require('../package.json');
function fatal(message, ...args) {
if (message instanceof Error) {
message = message.message;
if (/^cancell?ed$/i.test(message)) {
return;
}
}
util_1.log.error(message, ...args);
if (/Unauthorized\(401\)/.test(message)) {
util_1.log.error(`Be sure to use a Personal Access Token which has access to **all accessible accounts**.
See https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions for more information.`);
}
process.exit(1);
}
function main(task) {
let latestVersion = null;
const token = new util_1.CancellationToken();
if (tty_1.isatty(1)) {
npm_1.getLatestVersion(pkg.name, token)
.then(version => latestVersion = version)
.catch(_ => { });
}
task
.catch(fatal)
.then(() => {
if (latestVersion && semver.gt(latestVersion, pkg.version)) {
util_1.log.info(`\nThe latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}`);
}
else {
token.cancel();
}
});
}
module.exports = function (argv) {
program
.version(pkg.version)
.usage('<command> [options]');
program
.command('ls')
.description('Lists all the files that will be published')
.option('--yarn', 'Use yarn instead of npm')
.option('--packagedDependencies <path>', 'Select packages that should be published only (includes dependencies)', (val, all) => all ? all.concat(val) : [val], undefined)
.action(({ yarn, packagedDependencies }) => main(package_1.ls(undefined, yarn, packagedDependencies)));
program
.command('package')
.description('Packages an extension')
.option('-o, --out [path]', 'Output .vsix extension file to [path] location')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md with this url.')
.option('--yarn', 'Use yarn instead of npm')
.action(({ out, baseContentUrl, baseImagesUrl, yarn }) => main(package_1.packageCommand({ packagePath: out, baseContentUrl, baseImagesUrl, useYarn: yarn })));
program
.command('publish [<version>]')
.description('Publishes an extension')
.option('-p, --pat <token>', 'Personal Access Token')
.option('-m, --message <commit message>', 'Commit message used when calling `npm version`.')
.option('--packagePath [path]', 'Publish the VSIX package located at the specified path.')
.option('--baseContentUrl [url]', 'Prepend all relative links in README.md with this url.')
.option('--baseImagesUrl [url]', 'Prepend all relative image links in README.md with this url.')
.option('--yarn', 'Use yarn instead of npm while packing extension files')
.option('--noVerify')
.action((version, { pat, message, packagePath, baseContentUrl, baseImagesUrl, yarn, noVerify }) => main(publish_1.publish({ pat, commitMessage: message, version, packagePath, baseContentUrl, baseImagesUrl, useYarn: yarn, noVerify })));
program
.command('unpublish [<extensionid>]')
.description('Unpublishes an extension. Example extension id: microsoft.csharp.')
.option('-p, --pat <token>', 'Personal Access Token')
.action((id, { pat }) => main(publish_1.unpublish({ id, pat })));
program
.command('ls-publishers')
.description('List all known publishers')
.action(() => main(store_1.listPublishers()));
program
.command('create-publisher <publisher>')
.description('Creates a new publisher')
.action(publisher => main(store_1.createPublisher(publisher)));
program
.command('delete-publisher <publisher>')
.description('Deletes a publisher')
.action(publisher => main(store_1.deletePublisher(publisher)));
program
.command('login <publisher>')
.description('Add a publisher to the known publishers list')
.action(name => main(store_1.loginPublisher(name)));
program
.command('logout <publisher>')
.description('Remove a publisher from the known publishers list')
.action(name => main(store_1.logoutPublisher(name)));
program
.command('show <extensionid>')
.option('--json', 'Output data in json format', false)
.description('Show extension metadata')
.action((extensionid, { json }) => main(show_1.show(extensionid, json)));
program
.command('search <text>')
.option('--json', 'Output result in json format', false)
.description('search extension gallery')
.action((text, { json }) => main(search_1.search(text, json)));
program
.command('*', '', { noHelp: true })
.action((cmd) => {
program.help(help => {
const suggestion = didYouMean(cmd, program.commands.map(c => c._name));
help = `${help}
Unknown command '${cmd}'`;
return suggestion ? `${help}, did you mean '${suggestion}'?\n` : `${help}.\n`;
});
});
program.parse(argv);
if (process.argv.length <= 2) {
program.help();
}
};
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+20
View File
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const regex = /^%([\w\d.]+)%$/i;
function patcher(translations) {
return value => {
if (typeof value !== 'string') {
return;
}
const match = regex.exec(value);
if (!match) {
return;
}
return translations[match[1]] || value;
};
}
function patchNLS(manifest, translations) {
return lodash_1.cloneDeepWith(manifest, patcher(translations));
}
exports.patchNLS = patchNLS;
+159
View File
@@ -0,0 +1,159 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const fs = require("fs");
const cp = require("child_process");
const parseSemver = require("parse-semver");
const _ = require("lodash");
function parseStdout({ stdout }) {
return stdout.split(/[\r\n]/).filter(line => !!line)[0];
}
function exec(command, options = {}, cancellationToken) {
return new Promise((c, e) => {
let disposeCancellationListener = null;
const child = cp.exec(command, Object.assign({}, options, { encoding: 'utf8' }), (err, stdout, stderr) => {
if (disposeCancellationListener) {
disposeCancellationListener();
disposeCancellationListener = null;
}
if (err) {
return e(err);
}
c({ stdout, stderr });
});
if (cancellationToken) {
disposeCancellationListener = cancellationToken.subscribe(err => {
child.kill();
e(err);
});
}
});
}
function checkNPM(cancellationToken) {
return exec('npm -v', {}, cancellationToken).then(({ stdout }) => {
const version = stdout.trim();
if (/^3\.7\.[0123]$/.test(version)) {
return Promise.reject(`npm@${version} doesn't work with vsce. Please update npm: npm install -g npm`);
}
});
}
function getNpmDependencies(cwd) {
return checkNPM()
.then(() => exec('npm list --production --parseable --depth=99999', { cwd, maxBuffer: 5000 * 1024 }))
.then(({ stdout }) => stdout
.split(/[\r\n]/)
.filter(dir => path.isAbsolute(dir)));
}
function asYarnDependency(prefix, tree, prune) {
if (prune && /@[\^~]/.test(tree.name)) {
return null;
}
let name;
try {
const parseResult = parseSemver(tree.name);
name = parseResult.name;
}
catch (err) {
name = tree.name.replace(/^([^@+])@.*$/, '$1');
}
const dependencyPath = path.join(prefix, name);
const children = [];
for (const child of (tree.children || [])) {
const dep = asYarnDependency(path.join(prefix, name, 'node_modules'), child, prune);
if (dep) {
children.push(dep);
}
}
return { name, path: dependencyPath, children };
}
function selectYarnDependencies(deps, packagedDependencies) {
const index = new class {
constructor() {
this.data = Object.create(null);
for (const dep of deps) {
if (this.data[dep.name]) {
throw Error(`Dependency seen more than once: ${dep.name}`);
}
this.data[dep.name] = dep;
}
}
find(name) {
let result = this.data[name];
if (!result) {
throw new Error(`Could not find dependency: ${name}`);
}
return result;
}
};
const reached = new class {
constructor() {
this.values = [];
}
add(dep) {
if (this.values.indexOf(dep) < 0) {
this.values.push(dep);
return true;
}
return false;
}
};
const visit = (name) => {
let dep = index.find(name);
if (!reached.add(dep)) {
// already seen -> done
return;
}
for (const child of dep.children) {
visit(child.name);
}
};
packagedDependencies.forEach(visit);
return reached.values;
}
function getYarnProductionDependencies(cwd, packagedDependencies) {
return __awaiter(this, void 0, void 0, function* () {
const raw = yield new Promise((c, e) => cp.exec('yarn list --prod --json', { cwd, encoding: 'utf8', env: Object.assign({}, process.env), maxBuffer: 5000 * 1024 }, (err, stdout) => err ? e(err) : c(stdout)));
const match = /^{"type":"tree".*$/m.exec(raw);
if (!match || match.length !== 1) {
throw new Error('Could not parse result of `yarn list --json`');
}
const usingPackagedDependencies = Array.isArray(packagedDependencies);
const trees = JSON.parse(match[0]).data.trees;
let result = trees
.map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies))
.filter(dep => !!dep);
if (usingPackagedDependencies) {
result = selectYarnDependencies(result, packagedDependencies);
}
return result;
});
}
function getYarnDependencies(cwd, packagedDependencies) {
return __awaiter(this, void 0, void 0, function* () {
const result = [cwd];
if (yield new Promise(c => fs.exists(path.join(cwd, 'yarn.lock'), c))) {
const deps = yield getYarnProductionDependencies(cwd, packagedDependencies);
const flatten = (dep) => { result.push(dep.path); dep.children.forEach(flatten); };
deps.forEach(flatten);
}
return _.uniq(result);
});
}
function getDependencies(cwd, useYarn = false, packagedDependencies) {
return useYarn ? getYarnDependencies(cwd, packagedDependencies) : getNpmDependencies(cwd);
}
exports.getDependencies = getDependencies;
function getLatestVersion(name, cancellationToken) {
return checkNPM(cancellationToken)
.then(() => exec(`npm show ${name} version`, {}, cancellationToken))
.then(parseStdout);
}
exports.getLatestVersion = getLatestVersion;
+752
View File
@@ -0,0 +1,752 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const cp = require("child_process");
const _ = require("lodash");
const yazl = require("yazl");
const nls_1 = require("./nls");
const util = require("./util");
const _glob = require("glob");
const minimatch = require("minimatch");
const denodeify = require("denodeify");
const markdownit = require("markdown-it");
const cheerio = require("cheerio");
const url = require("url");
const mime_1 = require("mime");
const urljoin = require("url-join");
const validation_1 = require("./validation");
const npm_1 = require("./npm");
const readFile = denodeify(fs.readFile);
const unlink = denodeify(fs.unlink);
const stat = denodeify(fs.stat);
const exec = denodeify(cp.exec, (err, stdout, stderr) => [err, { stdout, stderr }]);
const glob = denodeify(_glob);
const resourcesPath = path.join(path.dirname(__dirname), 'resources');
const vsixManifestTemplatePath = path.join(resourcesPath, 'extension.vsixmanifest');
const contentTypesTemplatePath = path.join(resourcesPath, '[Content_Types].xml');
const MinimatchOptions = { dot: true };
function read(file) {
if (file.contents) {
return Promise.resolve(file.contents).then(b => typeof b === 'string' ? b : b.toString('utf8'));
}
else {
return readFile(file.localPath, 'utf8');
}
}
exports.read = read;
class BaseProcessor {
constructor(manifest) {
this.manifest = manifest;
this.assets = [];
this.vsix = Object.create(null);
}
onFile(file) { return Promise.resolve(file); }
onEnd() { return Promise.resolve(null); }
}
exports.BaseProcessor = BaseProcessor;
function getUrl(url) {
if (!url) {
return null;
}
if (typeof url === 'string') {
return url;
}
return url.url;
}
function getRepositoryUrl(url) {
const result = getUrl(url);
if (/^[^\/]+\/[^\/]+$/.test(result)) {
return `https://github.com/${result}.git`;
}
return result;
}
// Contributed by Mozilla develpoer authors
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
function toExtensionTags(extensions) {
return extensions
.map(s => s.replace(/\W/g, ''))
.filter(s => !!s)
.map(s => `__ext_${s}`);
}
function toLanguagePackTags(translations, languageId) {
return (translations || [])
.map(({ id }) => [`__lp_${id}`, `__lp-${languageId}_${id}`])
.reduce((r, t) => [...r, ...t], []);
}
/* This list is also maintained by the Marketplace team.
* Remember to reach out to them when adding new domains.
*/
const TrustedSVGSources = [
'api.bintray.com',
'api.travis-ci.com',
'api.travis-ci.org',
'app.fossa.io',
'badge.buildkite.com',
'badge.fury.io',
'badge.waffle.io',
'badgen.net',
'badges.frapsoft.com',
'badges.gitter.im',
'badges.greenkeeper.io',
'cdn.travis-ci.com',
'cdn.travis-ci.org',
'ci.appveyor.com',
'circleci.com',
'cla.opensource.microsoft.com',
'codacy.com',
'codeclimate.com',
'codecov.io',
'coveralls.io',
'david-dm.org',
'deepscan.io',
'dev.azure.com',
'docs.rs',
'flat.badgen.net',
'gemnasium.com',
'githost.io',
'gitlab.com',
'godoc.org',
'goreportcard.com',
'img.shields.io',
'isitmaintained.com',
'marketplace.visualstudio.com',
'nodesecurity.io',
'opencollective.com',
'snyk.io',
'travis-ci.com',
'travis-ci.org',
'visualstudio.com',
'vsmarketplacebadge.apphb.com',
'www.bithound.io',
'www.versioneye.com'
];
function isHostTrusted(host) {
return TrustedSVGSources.indexOf(host.toLowerCase()) > -1;
}
function isGitHubRepository(repository) {
return /^https:\/\/github\.com\/|^git@github\.com:/.test(repository || '');
}
class ManifestProcessor extends BaseProcessor {
constructor(manifest) {
super(manifest);
const flags = ['Public'];
if (manifest.preview) {
flags.push('Preview');
}
const repository = getRepositoryUrl(manifest.repository);
const isGitHub = isGitHubRepository(repository);
let enableMarketplaceQnA;
let customerQnALink;
if (manifest.qna === 'marketplace') {
enableMarketplaceQnA = true;
}
else if (typeof manifest.qna === 'string') {
customerQnALink = manifest.qna;
}
else if (manifest.qna === false) {
enableMarketplaceQnA = false;
}
this.vsix = Object.assign({}, this.vsix, { id: manifest.name, displayName: manifest.displayName || manifest.name, version: manifest.version, publisher: manifest.publisher, engine: manifest.engines['vscode'], description: manifest.description || '', categories: (manifest.categories || []).join(','), flags: flags.join(' '), links: {
repository,
bugs: getUrl(manifest.bugs),
homepage: manifest.homepage
}, galleryBanner: manifest.galleryBanner || {}, badges: manifest.badges, githubMarkdown: manifest.markdown !== 'standard', enableMarketplaceQnA,
customerQnALink, extensionDependencies: _(manifest.extensionDependencies || []).uniq().join(','), extensionPack: _(manifest.extensionPack || []).uniq().join(','), localizedLanguages: (manifest.contributes && manifest.contributes.localizations) ?
manifest.contributes.localizations.map(loc => loc.localizedLanguageName || loc.languageName || loc.languageId).join(',') : '' });
if (isGitHub) {
this.vsix.links.github = repository;
}
}
onEnd() {
return __awaiter(this, void 0, void 0, function* () {
if (this.manifest.publisher === 'vscode-samples') {
throw new Error('It\'s not allowed to use the \'vscode-samples\' publisher. Learn more at: https://code.visualstudio.com/api/working-with-extensions/publishing-extension.');
}
if (!this.manifest.repository) {
util.log.warn(`A 'repository' field is missing from the 'package.json' manifest file.`);
if (!/^y$/i.test(yield util.read('Do you want to continue? [y/N] '))) {
throw new Error('Aborted');
}
}
});
}
}
class TagsProcessor extends BaseProcessor {
onEnd() {
const keywords = this.manifest.keywords || [];
const contributes = this.manifest.contributes;
const activationEvents = this.manifest.activationEvents || [];
const doesContribute = name => contributes && contributes[name] && contributes[name].length > 0;
const colorThemes = doesContribute('themes') ? ['theme', 'color-theme'] : [];
const iconThemes = doesContribute('iconThemes') ? ['theme', 'icon-theme'] : [];
const snippets = doesContribute('snippets') ? ['snippet'] : [];
const keybindings = doesContribute('keybindings') ? ['keybindings'] : [];
const debuggers = doesContribute('debuggers') ? ['debuggers'] : [];
const json = doesContribute('jsonValidation') ? ['json'] : [];
const localizationContributions = ((contributes && contributes['localizations']) || [])
.reduce((r, l) => [...r, `lp-${l.languageId}`, ...toLanguagePackTags(l.translations, l.languageId)], []);
const languageContributions = ((contributes && contributes['languages']) || [])
.reduce((r, l) => [...r, l.id, ...(l.aliases || []), ...toExtensionTags(l.extensions || [])], []);
const languageActivations = activationEvents
.map(e => /^onLanguage:(.*)$/.exec(e))
.filter(r => !!r)
.map(r => r[1]);
const grammars = ((contributes && contributes['grammars']) || [])
.map(g => g.language);
const description = this.manifest.description || '';
const descriptionKeywords = Object.keys(TagsProcessor.Keywords)
.reduce((r, k) => r.concat(new RegExp('\\b(?:' + escapeRegExp(k) + ')(?!\\w)', 'gi').test(description) ? TagsProcessor.Keywords[k] : []), []);
const tags = [
...keywords,
...colorThemes,
...iconThemes,
...snippets,
...keybindings,
...debuggers,
...json,
...localizationContributions,
...languageContributions,
...languageActivations,
...grammars,
...descriptionKeywords
];
this.vsix.tags = _(tags)
.uniq() // deduplicate
.compact() // remove falsey values
.join(',');
return Promise.resolve(null);
}
}
TagsProcessor.Keywords = {
'git': ['git'],
'npm': ['node'],
'spell': ['markdown'],
'bootstrap': ['bootstrap'],
'lint': ['linters'],
'linting': ['linters'],
'react': ['javascript'],
'js': ['javascript'],
'node': ['javascript', 'node'],
'c++': ['c++'],
'Cplusplus': ['c++'],
'xml': ['xml'],
'angular': ['javascript'],
'jquery': ['javascript'],
'php': ['php'],
'python': ['python'],
'latex': ['latex'],
'ruby': ['ruby'],
'java': ['java'],
'erlang': ['erlang'],
'sql': ['sql'],
'nodejs': ['node'],
'c#': ['c#'],
'css': ['css'],
'javascript': ['javascript'],
'ftp': ['ftp'],
'haskell': ['haskell'],
'unity': ['unity'],
'terminal': ['terminal'],
'powershell': ['powershell'],
'laravel': ['laravel'],
'meteor': ['meteor'],
'emmet': ['emmet'],
'eslint': ['linters'],
'tfs': ['tfs'],
'rust': ['rust']
};
exports.TagsProcessor = TagsProcessor;
class MarkdownProcessor extends BaseProcessor {
constructor(manifest, name, regexp, assetType, options = {}) {
super(manifest);
this.name = name;
this.regexp = regexp;
this.assetType = assetType;
const guess = this.guessBaseUrls();
this.baseContentUrl = options.baseContentUrl || (guess && guess.content);
this.baseImagesUrl = options.baseImagesUrl || options.baseContentUrl || (guess && guess.images);
this.repositoryUrl = (guess && guess.repository);
this.isGitHub = isGitHubRepository(this.repositoryUrl);
}
onFile(file) {
return __awaiter(this, void 0, void 0, function* () {
const path = util.normalize(file.path);
if (!this.regexp.test(path)) {
return Promise.resolve(file);
}
this.assets.push({ type: this.assetType, path });
let contents = yield read(file);
if (/This is the README for your extension /.test(contents)) {
throw new Error(`Make sure to edit the README.md file before you publish your extension.`);
}
const markdownPathRegex = /(!?)\[([^\]\[]*|!\[[^\]\[]*]\([^\)]+\))\]\(([^\)]+)\)/g;
const urlReplace = (all, isImage, title, link) => {
const isLinkRelative = !/^\w+:\/\//.test(link) && link[0] !== '#';
if (!this.baseContentUrl && !this.baseImagesUrl) {
const asset = isImage ? 'image' : 'link';
if (isLinkRelative) {
throw new Error(`Couldn't detect the repository where this extension is published. The ${asset} '${link}' will be broken in ${this.name}. Please provide the repository URL in package.json or use the --baseContentUrl and --baseImagesUrl options.`);
}
}
title = title.replace(markdownPathRegex, urlReplace);
const prefix = isImage ? this.baseImagesUrl : this.baseContentUrl;
if (!prefix || !isLinkRelative) {
return `${isImage}[${title}](${link})`;
}
return `${isImage}[${title}](${urljoin(prefix, link)})`;
};
// Replace Markdown links with urls
contents = contents.replace(markdownPathRegex, urlReplace);
const markdownIssueRegex = /(\s|\n)([\w\d_-]+\/[\w\d_-]+)?#(\d+)\b/g;
const issueReplace = (all, prefix, ownerAndRepositoryName, issueNumber) => {
let result = all;
let owner;
let repositoryName;
if (ownerAndRepositoryName) {
[owner, repositoryName] = ownerAndRepositoryName.split('/', 2);
}
if (this.isGitHub) {
if (owner && repositoryName && issueNumber) {
// Issue in external repository
const issueUrl = urljoin('https://github.com', owner, repositoryName, 'issues', issueNumber);
result = prefix + `[${owner}/${repositoryName}#${issueNumber}](${issueUrl})`;
}
else if (!owner && !repositoryName && issueNumber) {
// Issue in own repository
result = prefix + `[#${issueNumber}](${urljoin(this.repositoryUrl, 'issues', issueNumber)})`;
}
}
return result;
};
// Replace Markdown issue references with urls
contents = contents.replace(markdownIssueRegex, issueReplace);
const html = markdownit({ html: true }).render(contents);
const $ = cheerio.load(html);
$('img').each((_, img) => {
const src = decodeURI(img.attribs.src);
const srcUrl = url.parse(src);
if (/^data:$/i.test(srcUrl.protocol) && /^image$/i.test(srcUrl.host) && /\/svg/i.test(srcUrl.path)) {
throw new Error(`SVG data URLs are not allowed in ${this.name}: ${src}`);
}
if (!/^https:$/i.test(srcUrl.protocol)) {
throw new Error(`Images in ${this.name} must come from an HTTPS source: ${src}`);
}
if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl.host)) {
throw new Error(`SVGs are restricted in ${this.name}; please use other file image formats, such as PNG: ${src}`);
}
});
$('svg').each((_, svg) => {
throw new Error(`SVG tags are not allowed in ${this.name}.`);
});
return {
path: file.path,
contents: new Buffer(contents)
};
});
}
// GitHub heuristics
guessBaseUrls() {
let repository = null;
if (typeof this.manifest.repository === 'string') {
repository = this.manifest.repository;
}
else if (this.manifest.repository && typeof this.manifest.repository['url'] === 'string') {
repository = this.manifest.repository['url'];
}
if (!repository) {
return null;
}
const regex = /github\.com\/([^/]+)\/([^/]+)(\/|$)/;
const match = regex.exec(repository);
if (!match) {
return null;
}
const account = match[1];
const repositoryName = match[2].replace(/\.git$/i, '');
return {
content: `https://github.com/${account}/${repositoryName}/blob/master`,
images: `https://github.com/${account}/${repositoryName}/raw/master`,
repository: `https://github.com/${account}/${repositoryName}`
};
}
}
exports.MarkdownProcessor = MarkdownProcessor;
class ReadmeProcessor extends MarkdownProcessor {
constructor(manifest, options = {}) {
super(manifest, 'README.md', /^extension\/readme.md$/i, 'Microsoft.VisualStudio.Services.Content.Details', options);
}
}
exports.ReadmeProcessor = ReadmeProcessor;
class ChangelogProcessor extends MarkdownProcessor {
constructor(manifest, options = {}) {
super(manifest, 'CHANGELOG.md', /^extension\/changelog.md$/i, 'Microsoft.VisualStudio.Services.Content.Changelog', options);
}
}
exports.ChangelogProcessor = ChangelogProcessor;
class LicenseProcessor extends BaseProcessor {
constructor(manifest) {
super(manifest);
this.didFindLicense = false;
const match = /^SEE LICENSE IN (.*)$/.exec(manifest.license || '');
if (!match || !match[1]) {
this.filter = name => /^extension\/license(\.(md|txt))?$/i.test(name);
}
else {
const regexp = new RegExp('^extension/' + match[1] + '$');
this.filter = regexp.test.bind(regexp);
}
this.vsix.license = null;
}
onFile(file) {
if (!this.didFindLicense) {
let normalizedPath = util.normalize(file.path);
if (this.filter(normalizedPath)) {
if (!path.extname(normalizedPath)) {
file.path += '.txt';
normalizedPath += '.txt';
}
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Content.License', path: normalizedPath });
this.vsix.license = normalizedPath;
this.didFindLicense = true;
}
}
return Promise.resolve(file);
}
}
class IconProcessor extends BaseProcessor {
constructor(manifest) {
super(manifest);
this.didFindIcon = false;
this.icon = manifest.icon ? `extension/${manifest.icon}` : null;
this.vsix.icon = null;
}
onFile(file) {
const normalizedPath = util.normalize(file.path);
if (normalizedPath === this.icon) {
this.didFindIcon = true;
this.assets.push({ type: 'Microsoft.VisualStudio.Services.Icons.Default', path: normalizedPath });
this.vsix.icon = this.icon;
}
return Promise.resolve(file);
}
onEnd() {
if (this.icon && !this.didFindIcon) {
return Promise.reject(new Error(`The specified icon '${this.icon}' wasn't found in the extension.`));
}
return Promise.resolve(null);
}
}
class NLSProcessor extends BaseProcessor {
constructor(manifest) {
super(manifest);
this.translations = Object.create(null);
if (!manifest.contributes || !manifest.contributes.localizations || manifest.contributes.localizations.length === 0) {
return;
}
const localizations = manifest.contributes.localizations;
const translations = Object.create(null);
// take last reference in the manifest for any given language
for (const localization of localizations) {
for (const translation of localization.translations) {
if (translation.id === 'vscode' && !!translation.path) {
const translationPath = util.normalize(translation.path.replace(/^\.[\/\\]/, ''));
translations[localization.languageId.toUpperCase()] = `extension/${translationPath}`;
}
}
}
// invert the map for later easier retrieval
for (const languageId of Object.keys(translations)) {
this.translations[translations[languageId]] = languageId;
}
}
onFile(file) {
const normalizedPath = util.normalize(file.path);
const language = this.translations[normalizedPath];
if (language) {
this.assets.push({ type: `Microsoft.VisualStudio.Code.Translation.${language}`, path: normalizedPath });
}
return Promise.resolve(file);
}
}
exports.NLSProcessor = NLSProcessor;
function validateManifest(manifest) {
validation_1.validatePublisher(manifest.publisher);
validation_1.validateExtensionName(manifest.name);
if (!manifest.version) {
throw new Error('Manifest missing field: version');
}
validation_1.validateVersion(manifest.version);
if (!manifest.engines) {
throw new Error('Manifest missing field: engines');
}
if (!manifest.engines['vscode']) {
throw new Error('Manifest missing field: engines.vscode');
}
validation_1.validateEngineCompatibility(manifest.engines['vscode']);
if (manifest.devDependencies && manifest.devDependencies['@types/vscode']) {
validation_1.validateVSCodeTypesCompatibility(manifest.engines['vscode'], manifest.devDependencies['@types/vscode']);
}
if (/\.svg$/i.test(manifest.icon || '')) {
throw new Error(`SVGs can't be used as icons: ${manifest.icon}`);
}
(manifest.badges || []).forEach(badge => {
const decodedUrl = decodeURI(badge.url);
const srcUrl = url.parse(decodedUrl);
if (!/^https:$/i.test(srcUrl.protocol)) {
throw new Error(`Badge URLs must come from an HTTPS source: ${badge.url}`);
}
if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl.host)) {
throw new Error(`Badge SVGs are restricted. Please use other file image formats, such as PNG: ${badge.url}`);
}
});
Object.keys((manifest.dependencies || {})).forEach(dep => {
if (dep === 'vscode') {
throw new Error(`You should not depend on 'vscode' in your 'dependencies'. Did you mean to add it to 'devDependencies'?`);
}
});
return manifest;
}
exports.validateManifest = validateManifest;
function readManifest(cwd = process.cwd(), nls = true) {
const manifestPath = path.join(cwd, 'package.json');
const manifestNLSPath = path.join(cwd, 'package.nls.json');
const manifest = readFile(manifestPath, 'utf8')
.catch(() => Promise.reject(`Extension manifest not found: ${manifestPath}`))
.then(manifestStr => {
try {
return Promise.resolve(JSON.parse(manifestStr));
}
catch (e) {
return Promise.reject(`Error parsing 'package.json' manifest file: not a valid JSON file.`);
}
})
.then(validateManifest);
if (!nls) {
return manifest;
}
const manifestNLS = readFile(manifestNLSPath, 'utf8')
.catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}'))
.then(raw => {
try {
return Promise.resolve(JSON.parse(raw));
}
catch (e) {
return Promise.reject(`Error parsing JSON manifest translations file: ${manifestNLSPath}`);
}
});
return Promise.all([manifest, manifestNLS]).then(([manifest, translations]) => {
return nls_1.patchNLS(manifest, translations);
});
}
exports.readManifest = readManifest;
function toVsixManifest(assets, vsix, options = {}) {
return readFile(vsixManifestTemplatePath, 'utf8')
.then(vsixManifestTemplateStr => _.template(vsixManifestTemplateStr))
.then(vsixManifestTemplate => vsixManifestTemplate(vsix));
}
exports.toVsixManifest = toVsixManifest;
const defaultExtensions = {
'.json': 'application/json',
'.vsixmanifest': 'text/xml'
};
function toContentTypes(files) {
const extensions = Object.keys(_.keyBy(files, f => path.extname(f.path).toLowerCase()))
.filter(e => !!e)
.reduce((r, e) => (Object.assign({}, r, { [e]: mime_1.lookup(e) })), {});
const allExtensions = Object.assign({}, extensions, defaultExtensions);
const contentTypes = Object.keys(allExtensions).map(extension => ({
extension,
contentType: allExtensions[extension]
}));
return readFile(contentTypesTemplatePath, 'utf8')
.then(contentTypesTemplateStr => _.template(contentTypesTemplateStr))
.then(contentTypesTemplate => contentTypesTemplate({ contentTypes }));
}
exports.toContentTypes = toContentTypes;
const defaultIgnore = [
'.vscodeignore',
'package-lock.json',
'yarn.lock',
'.editorconfig',
'.npmrc',
'.yarnrc',
'.gitattributes',
'*.todo',
'tslint.yaml',
'.eslintrc*',
'.babelrc*',
'.prettierrc',
'ISSUE_TEMPLATE.md',
'CONTRIBUTING.md',
'PULL_REQUEST_TEMPLATE.md',
'CODE_OF_CONDUCT.md',
'.github',
'.travis.yml',
'appveyor.yml',
'**/.git/**',
'**/*.vsix',
'**/.DS_Store',
'**/*.vsixmanifest',
'**/.vscode-test/**'
];
function collectAllFiles(cwd, useYarn = false, dependencyEntryPoints) {
return npm_1.getDependencies(cwd, useYarn, dependencyEntryPoints).then(deps => {
const promises = deps.map(dep => {
return glob('**', { cwd: dep, nodir: true, dot: true, ignore: 'node_modules/**' })
.then(files => files
.map(f => path.relative(cwd, path.join(dep, f)))
.map(f => f.replace(/\\/g, '/')));
});
return Promise.all(promises).then(util.flatten);
});
}
function collectFiles(cwd, useYarn = false, dependencyEntryPoints) {
return collectAllFiles(cwd, useYarn, dependencyEntryPoints).then(files => {
files = files.filter(f => !/\r$/m.test(f));
return readFile(path.join(cwd, '.vscodeignore'), 'utf8')
.catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve(''))
// Parse raw ignore by splitting output into lines and filtering out empty lines and comments
.then(rawIgnore => rawIgnore.split(/[\n\r]/).map(s => s.trim()).filter(s => !!s).filter(i => !/^\s*#/.test(i)))
// Add '/**' to possible folder names
.then(ignore => [...ignore, ...ignore.filter(i => !/(^|\/)[^/]*\*[^/]*$/.test(i)).map(i => /\/$/.test(i) ? `${i}**` : `${i}/**`)])
// Combine with default ignore list
.then(ignore => [...defaultIgnore, ...ignore, '!package.json'])
// Split into ignore and negate list
.then(ignore => _.partition(ignore, i => !/^\s*!/.test(i)))
.then(r => ({ ignore: r[0], negate: r[1] }))
// Filter out files
.then(({ ignore, negate }) => files.filter(f => !ignore.some(i => minimatch(f, i, MinimatchOptions)) || negate.some(i => minimatch(f, i.substr(1), MinimatchOptions))));
});
}
function processFiles(processors, files, options = {}) {
const processedFiles = files.map(file => util.chain(file, processors, (file, processor) => processor.onFile(file)));
return Promise.all(processedFiles).then(files => {
return util.sequence(processors.map(p => () => p.onEnd())).then(() => {
const assets = _.flatten(processors.map(p => p.assets));
const vsix = processors.reduce((r, p) => (Object.assign({}, r, p.vsix)), { assets });
return Promise.all([toVsixManifest(assets, vsix, options), toContentTypes(files)]).then(result => {
return [
{ path: 'extension.vsixmanifest', contents: new Buffer(result[0], 'utf8') },
{ path: '[Content_Types].xml', contents: new Buffer(result[1], 'utf8') },
...files
];
});
});
});
}
exports.processFiles = processFiles;
function createDefaultProcessors(manifest, options = {}) {
return [
new ManifestProcessor(manifest),
new TagsProcessor(manifest),
new ReadmeProcessor(manifest, options),
new ChangelogProcessor(manifest, options),
new LicenseProcessor(manifest),
new IconProcessor(manifest),
new NLSProcessor(manifest)
];
}
exports.createDefaultProcessors = createDefaultProcessors;
function collect(manifest, options = {}) {
const cwd = options.cwd || process.cwd();
const useYarn = options.useYarn || false;
const packagedDependencies = options.dependencyEntryPoints || undefined;
const processors = createDefaultProcessors(manifest, options);
return collectFiles(cwd, useYarn, packagedDependencies).then(fileNames => {
const files = fileNames.map(f => ({ path: `extension/${f}`, localPath: path.join(cwd, f) }));
return processFiles(processors, files, options);
});
}
exports.collect = collect;
function writeVsix(files, packagePath) {
return unlink(packagePath)
.catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve(null))
.then(() => new Promise((c, e) => {
const zip = new yazl.ZipFile();
files.forEach(f => f.contents ? zip.addBuffer(typeof f.contents === 'string' ? new Buffer(f.contents, 'utf8') : f.contents, f.path) : zip.addFile(f.localPath, f.path));
zip.end();
const zipStream = fs.createWriteStream(packagePath);
zip.outputStream.pipe(zipStream);
zip.outputStream.once('error', e);
zipStream.once('error', e);
zipStream.once('finish', () => c(packagePath));
}));
}
function defaultPackagePath(cwd, manifest) {
return path.join(cwd, `${manifest.name}-${manifest.version}.vsix`);
}
function prepublish(cwd, manifest, useYarn = false) {
if (!manifest.scripts || !manifest.scripts['vscode:prepublish']) {
return Promise.resolve(manifest);
}
console.warn(`Executing prepublish script '${useYarn ? 'yarn' : 'npm'} run vscode:prepublish'...`);
return exec(`${useYarn ? 'yarn' : 'npm'} run vscode:prepublish`, { cwd, maxBuffer: 5000 * 1024 })
.then(({ stdout, stderr }) => {
process.stdout.write(stdout);
process.stderr.write(stderr);
return Promise.resolve(manifest);
})
.catch(err => Promise.reject(err.message));
}
function pack(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const cwd = options.cwd || process.cwd();
let manifest = yield readManifest(cwd);
manifest = yield prepublish(cwd, manifest, options.useYarn);
const files = yield collect(manifest, options);
if (files.length > 100) {
console.log(`This extension consists of ${files.length} separate files. For performance reasons, you should bundle your extension: https://aka.ms/vscode-bundle-extension. You should also exclude unnecessary files by adding them to your .vscodeignore: https://aka.ms/vscode-vscodeignore`);
}
const packagePath = yield writeVsix(files, path.resolve(options.packagePath || defaultPackagePath(cwd, manifest)));
return { manifest, packagePath, files };
});
}
exports.pack = pack;
function packageCommand(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
const { packagePath, files } = yield pack(options);
const stats = yield stat(packagePath);
let size = 0;
let unit = '';
if (stats.size > 1048576) {
size = Math.round(stats.size / 10485.76) / 100;
unit = 'MB';
}
else {
size = Math.round(stats.size / 10.24) / 100;
unit = 'KB';
}
util.log.done(`Packaged: ${packagePath} (${files.length} files, ${size}${unit})`);
});
}
exports.packageCommand = packageCommand;
/**
* Lists the files included in the extension's package. Does not run prepublish.
*/
function listFiles(cwd = process.cwd(), useYarn = false, packagedDependencies) {
return readManifest(cwd)
.then(manifest => collectFiles(cwd, useYarn, packagedDependencies));
}
exports.listFiles = listFiles;
/**
* Lists the files included in the extension's package. Runs prepublish.
*/
function ls(cwd = process.cwd(), useYarn = false, packagedDependencies) {
return readManifest(cwd)
.then(manifest => prepublish(cwd, manifest, useYarn))
.then(manifest => collectFiles(cwd, useYarn, packagedDependencies))
.then(files => files.forEach(f => console.log(`${f}`)));
}
exports.ls = ls;
+43
View File
@@ -0,0 +1,43 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const HttpClient_1 = require("typed-rest-client/HttpClient");
const GalleryInterfaces_1 = require("azure-devops-node-api/interfaces/GalleryInterfaces");
const Serialization_1 = require("azure-devops-node-api/Serialization");
class PublicGalleryAPI {
constructor(baseUrl, apiVersion = '3.0-preview.1') {
this.baseUrl = baseUrl;
this.apiVersion = apiVersion;
this.client = new HttpClient_1.HttpClient('vsce');
}
post(url, data, additionalHeaders) {
return this.client.post(`${this.baseUrl}/_apis/public${url}`, data, additionalHeaders);
}
extensionQuery({ pageNumber = 1, pageSize = 1, sortBy = GalleryInterfaces_1.SortByType.Relevance, sortOrder = GalleryInterfaces_1.SortOrderType.Default, flags = [], criteria = [], assetTypes = [], }) {
return __awaiter(this, void 0, void 0, function* () {
const data = JSON.stringify({
filters: [{ pageNumber, pageSize, criteria }],
assetTypes,
flags: flags.reduce((memo, flag) => memo | flag, 0)
});
const res = yield this.post('/gallery/extensionquery', data, { Accept: `application/json;api-version=${this.apiVersion}`, 'Content-Type': 'application/json', });
const raw = JSON.parse(yield res.readBody());
return Serialization_1.ContractSerializer.deserialize(raw.results[0].extensions, GalleryInterfaces_1.TypeInfo.PublishedExtension, false, false);
});
}
getExtension(extensionId, flags = []) {
return __awaiter(this, void 0, void 0, function* () {
const query = { criteria: [{ filterType: GalleryInterfaces_1.ExtensionQueryFilterType.Name, value: extensionId }], flags, };
const extensions = yield this.extensionQuery(query);
return extensions.filter(({ publisher: { publisherName: publisher }, extensionName: name }) => extensionId.toLowerCase() === `${publisher}.${name}`.toLowerCase())[0];
});
}
}
exports.PublicGalleryAPI = PublicGalleryAPI;
+169
View File
@@ -0,0 +1,169 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const GalleryInterfaces_1 = require("azure-devops-node-api/interfaces/GalleryInterfaces");
const package_1 = require("./package");
const tmp = require("tmp");
const store_1 = require("./store");
const util_1 = require("./util");
const denodeify = require("denodeify");
const yauzl = require("yauzl");
const semver = require("semver");
const cp = require("child_process");
const exec = denodeify(cp.exec, (err, stdout, stderr) => [err, { stdout, stderr }]);
const tmpName = denodeify(tmp.tmpName);
function readManifestFromPackage(packagePath) {
return new Promise((c, e) => {
yauzl.open(packagePath, (err, zipfile) => {
if (err) {
return e(err);
}
const onEnd = () => e(new Error('Manifest not found'));
zipfile.once('end', onEnd);
zipfile.on('entry', entry => {
if (!/^extension\/package\.json$/i.test(entry.fileName)) {
return;
}
zipfile.removeListener('end', onEnd);
zipfile.openReadStream(entry, (err, stream) => {
if (err) {
return e(err);
}
const buffers = [];
stream.on('data', buffer => buffers.push(buffer));
stream.once('error', e);
stream.once('end', () => {
try {
c(JSON.parse(Buffer.concat(buffers).toString('utf8')));
}
catch (err) {
e(err);
}
});
});
});
});
});
}
function _publish(packagePath, pat, manifest) {
return __awaiter(this, void 0, void 0, function* () {
const api = yield util_1.getGalleryAPI(pat);
const packageStream = fs.createReadStream(packagePath);
const name = `${manifest.publisher}.${manifest.name}`;
const fullName = `${name}@${manifest.version}`;
console.log(`Publishing ${fullName}...`);
return api.getExtension(null, manifest.publisher, manifest.name, null, GalleryInterfaces_1.ExtensionQueryFlags.IncludeVersions)
.catch(err => err.statusCode === 404 ? null : Promise.reject(err))
.then(extension => {
if (extension && extension.versions.some(v => v.version === manifest.version)) {
return Promise.reject(`${fullName} already exists. Version number cannot be the same.`);
}
var promise = extension
? api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name)
: api.createExtension(undefined, packageStream);
return promise
.catch(err => Promise.reject(err.statusCode === 409 ? `${fullName} already exists.` : err))
.then(() => util_1.log.done(`Published ${fullName}\nYour extension will live at ${util_1.getPublishedUrl(name)} (might take a few seconds for it to show up).`));
})
.catch(err => {
const message = err && err.message || '';
if (/Invalid Resource/.test(message)) {
err.message = `${err.message}\n\nYou're likely using an expired Personal Access Token, please get a new PAT.\nMore info: https://aka.ms/vscodepat`;
}
return Promise.reject(err);
});
});
}
function versionBump(cwd = process.cwd(), version, commitMessage) {
if (!version) {
return Promise.resolve(null);
}
switch (version) {
case 'major':
case 'minor':
case 'patch':
break;
case 'premajor':
case 'preminor':
case 'prepatch':
case 'prerelease':
case 'from-git':
return Promise.reject(`Not supported: ${version}`);
default:
if (!semver.valid(version)) {
return Promise.reject(`Invalid version ${version}`);
}
}
let command = `npm version ${version}`;
if (commitMessage) {
command = `${command} -m "${commitMessage}"`;
}
// call `npm version` to do our dirty work
return exec(command, { cwd })
.then(({ stdout, stderr }) => {
process.stdout.write(stdout);
process.stderr.write(stderr);
return Promise.resolve(null);
})
.catch(err => Promise.reject(err.message));
}
function publish(options = {}) {
let promise;
if (options.packagePath) {
if (options.version) {
return Promise.reject(`Not supported: packagePath and version.`);
}
promise = readManifestFromPackage(options.packagePath)
.then(manifest => ({ manifest, packagePath: options.packagePath }));
}
else {
const cwd = options.cwd;
const baseContentUrl = options.baseContentUrl;
const baseImagesUrl = options.baseImagesUrl;
const useYarn = options.useYarn;
promise = versionBump(options.cwd, options.version, options.commitMessage)
.then(() => tmpName())
.then(packagePath => package_1.pack({ packagePath, cwd, baseContentUrl, baseImagesUrl, useYarn }));
}
return promise.then(({ manifest, packagePath }) => {
if (!options.noVerify && manifest.enableProposedApi) {
throw new Error('Extensions using proposed API (enableProposedApi: true) can\'t be published to the Marketplace');
}
const patPromise = options.pat
? Promise.resolve(options.pat)
: store_1.getPublisher(manifest.publisher).then(p => p.pat);
return patPromise.then(pat => _publish(packagePath, pat, manifest));
});
}
exports.publish = publish;
function unpublish(options = {}) {
let promise;
if (options.id) {
const [publisher, name] = options.id.split('.');
promise = Promise.resolve(({ publisher, name }));
}
else {
promise = package_1.readManifest(options.cwd);
}
return promise.then(({ publisher, name }) => {
const fullName = `${publisher}.${name}`;
const pat = options.pat
? Promise.resolve(options.pat)
: store_1.getPublisher(publisher).then(p => p.pat);
return util_1.read(`This will FOREVER delete '${fullName}'! Are you sure? [y/N] `)
.then(answer => /^y$/i.test(answer) ? null : Promise.reject('Aborted'))
.then(() => pat)
.then(util_1.getGalleryAPI)
.then(api => api.deleteExtension(publisher, name))
.then(() => util_1.log.done(`Deleted extension: ${fullName}!`));
});
}
exports.unpublish = unpublish;
+48
View File
@@ -0,0 +1,48 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const GalleryInterfaces_1 = require("azure-devops-node-api/interfaces/GalleryInterfaces");
const viewutils_1 = require("./viewutils");
const pageSize = 100;
function search(searchText, json = false, pageNumber = 1) {
return __awaiter(this, void 0, void 0, function* () {
const flags = [];
const api = util_1.getPublicGalleryAPI();
const results = yield api.extensionQuery({
pageSize,
criteria: [
{ filterType: GalleryInterfaces_1.ExtensionQueryFilterType.SearchText, value: searchText },
],
flags,
});
if (json) {
console.log(JSON.stringify(results, undefined, '\t'));
return;
}
if (!results.length) {
console.log('No matching results');
return;
}
console.log([
`Search results:`,
'',
...viewutils_1.tableView([
['<ExtensionId>', '<Description>'],
...results.map(({ publisher: { publisherName }, extensionName, shortDescription }) => [publisherName + '.' + extensionName, (shortDescription || '').replace(/\n|\r|\t/g, ' ')])
]),
'',
'For more information on an extension use "vsce show <extensionId>"',
]
.map(line => viewutils_1.wordTrim(line.replace(/\s+$/g, '')))
.join('\n'));
});
}
exports.search = search;
+74
View File
@@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("./util");
const GalleryInterfaces_1 = require("azure-devops-node-api/interfaces/GalleryInterfaces");
const viewutils_1 = require("./viewutils");
const limitVersions = 6;
const isExtensionTag = /^__ext_(.*)$/;
function show(extensionId, json = false) {
const flags = [
GalleryInterfaces_1.ExtensionQueryFlags.IncludeCategoryAndTags,
GalleryInterfaces_1.ExtensionQueryFlags.IncludeMetadata,
GalleryInterfaces_1.ExtensionQueryFlags.IncludeStatistics,
GalleryInterfaces_1.ExtensionQueryFlags.IncludeVersions,
];
return util_1.getPublicGalleryAPI()
.getExtension(extensionId, flags)
.then(extension => {
if (json) {
console.log(JSON.stringify(extension, undefined, '\t'));
}
else {
if (extension === undefined) {
util_1.log.error(`Extension "${extensionId}" not found.`);
}
else {
showOverview(extension);
}
}
});
}
exports.show = show;
function showOverview({ displayName = 'unknown', extensionName = 'unknown', shortDescription = '', versions = [], publisher: { displayName: publisherDisplayName, publisherName }, categories = [], tags = [], statistics = [], publishedDate, lastUpdated, }) {
const [{ version = 'unknown' } = {}] = versions;
// Create formatted table list of versions
const versionList = versions
.slice(0, limitVersions)
.map(({ version, lastUpdated }) => [version, viewutils_1.formatDate(lastUpdated)]);
const { install: installs = 0, averagerating = 0, ratingcount = 0, } = statistics
.reduce((map, { statisticName, value }) => (Object.assign({}, map, { [statisticName]: value })), {});
// Render
console.log([
`${displayName}`,
`${publisherDisplayName} | ${viewutils_1.icons.download} ` +
`${Number(installs).toLocaleString()} installs |` +
` ${viewutils_1.ratingStars(averagerating)} (${ratingcount})`,
'',
`${shortDescription}`,
'',
'Recent versions:',
...(versionList.length ? viewutils_1.tableView(versionList).map(viewutils_1.indentRow) : ['no versions found']),
'',
'Categories:',
` ${categories.join(', ')}`,
'',
'Tags:',
` ${tags.filter(tag => !isExtensionTag.test(tag)).join(', ')}`,
'',
'More info:',
...viewutils_1.tableView([
['Unique identifier:', `${publisherName}.${extensionName}`],
['Version:', version],
['Last updated:', viewutils_1.formatDateTime(lastUpdated)],
['Publisher:', publisherDisplayName],
['Published at:', viewutils_1.formatDate(publishedDate)],
])
.map(viewutils_1.indentRow),
'',
'Statistics:',
...viewutils_1.tableView(statistics.map(({ statisticName, value }) => [statisticName, Number(value).toFixed(2)]))
.map(viewutils_1.indentRow),
]
.map(line => viewutils_1.wordWrap(line))
.join('\n'));
}
+136
View File
@@ -0,0 +1,136 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const osenv_1 = require("osenv");
const util_1 = require("./util");
const validation_1 = require("./validation");
const denodeify = require("denodeify");
const readFile = denodeify(fs.readFile);
const writeFile = denodeify(fs.writeFile);
const storePath = path.join(osenv_1.home(), '.vsce');
function load() {
return readFile(storePath, 'utf8')
.catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}'))
.then(rawStore => {
try {
return Promise.resolve(JSON.parse(rawStore));
}
catch (e) {
return Promise.reject(`Error parsing store: ${storePath}`);
}
})
.then(store => {
store.publishers = store.publishers || [];
return Promise.resolve(store);
});
}
function save(store) {
return writeFile(storePath, JSON.stringify(store))
.then(() => store);
}
function addPublisherToStore(store, publisher) {
store.publishers = [...store.publishers.filter(p => p.name !== publisher.name), publisher];
return save(store).then(() => publisher);
}
function removePublisherFromStore(store, publisherName) {
store.publishers = store.publishers.filter(p => p.name !== publisherName);
return save(store);
}
function requestPAT(store, publisherName) {
return __awaiter(this, void 0, void 0, function* () {
const pat = yield util_1.read(`Personal Access Token for publisher '${publisherName}':`, { silent: true, replace: '*' });
// If the caller of the `getRoleAssignments` API has any of the roles
// (Creator, Owner, Contributor, Reader) on the publisher, we get a 200,
// otherwise we get a 403.
const api = yield util_1.getSecurityRolesAPI(pat);
yield api.getRoleAssignments('gallery.publisher', publisherName);
return yield addPublisherToStore(store, { name: publisherName, pat });
});
}
function getPublisher(publisherName) {
validation_1.validatePublisher(publisherName);
return load().then(store => {
const publisher = store.publishers.filter(p => p.name === publisherName)[0];
return publisher ? Promise.resolve(publisher) : requestPAT(store, publisherName);
});
}
exports.getPublisher = getPublisher;
function loginPublisher(publisherName) {
validation_1.validatePublisher(publisherName);
return load()
.then(store => {
const publisher = store.publishers.filter(p => p.name === publisherName)[0];
if (publisher) {
console.log(`Publisher '${publisherName}' is already known`);
return util_1.read('Do you want to overwrite its PAT? [y/N] ')
.then(answer => /^y$/i.test(answer) ? store : Promise.reject('Aborted'));
}
return Promise.resolve(store);
})
.then(store => requestPAT(store, publisherName));
}
exports.loginPublisher = loginPublisher;
function logoutPublisher(publisherName) {
validation_1.validatePublisher(publisherName);
return load().then(store => {
const publisher = store.publishers.filter(p => p.name === publisherName)[0];
if (!publisher) {
return Promise.reject(`Unknown publisher '${publisherName}'`);
}
return removePublisherFromStore(store, publisherName);
});
}
exports.logoutPublisher = logoutPublisher;
function createPublisher(publisherName) {
validation_1.validatePublisher(publisherName);
return util_1.read(`Publisher human-friendly name: `, { default: publisherName }).then(displayName => {
return util_1.read(`E-mail: `).then(email => {
return util_1.read(`Personal Access Token:`, { silent: true, replace: '*' })
.then((pat) => __awaiter(this, void 0, void 0, function* () {
const api = yield util_1.getGalleryAPI(pat);
const raw = {
publisherName,
displayName,
extensions: [],
flags: null,
lastUpdated: null,
longDescription: '',
publisherId: null,
shortDescription: '',
emailAddress: [email]
};
yield api.createPublisher(raw);
return { name: publisherName, pat };
}))
.then(publisher => load().then(store => addPublisherToStore(store, publisher)));
});
})
.then(() => util_1.log.done(`Created publisher '${publisherName}'.`));
}
exports.createPublisher = createPublisher;
function deletePublisher(publisherName) {
return getPublisher(publisherName).then(({ pat }) => {
return util_1.read(`This will FOREVER delete '${publisherName}'! Are you sure? [y/N] `)
.then(answer => /^y$/i.test(answer) ? null : Promise.reject('Aborted'))
.then(() => util_1.getGalleryAPI(pat))
.then(api => api.deletePublisher(publisherName))
.then(() => load().then(store => removePublisherFromStore(store, publisherName)))
.then(() => util_1.log.done(`Deleted publisher '${publisherName}'.`));
});
}
exports.deletePublisher = deletePublisher;
function listPublishers() {
return load()
.then(store => store.publishers)
.then(publishers => publishers.forEach(p => console.log(p.name)));
}
exports.listPublishers = listPublishers;
+138
View File
@@ -0,0 +1,138 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const _read = require("read");
const WebApi_1 = require("azure-devops-node-api/WebApi");
const GalleryApi_1 = require("azure-devops-node-api/GalleryApi");
const denodeify = require("denodeify");
const chalk_1 = require("chalk");
const publicgalleryapi_1 = require("./publicgalleryapi");
const __read = denodeify(_read);
function read(prompt, options = {}) {
if (process.env['VSCE_TESTS'] || !process.stdout.isTTY) {
return Promise.resolve('y');
}
return __read(Object.assign({ prompt }, options));
}
exports.read = read;
const marketplaceUrl = process.env['VSCE_MARKETPLACE_URL'] || 'https://marketplace.visualstudio.com';
function getPublishedUrl(extension) {
return `${marketplaceUrl}/items?itemName=${extension}`;
}
exports.getPublishedUrl = getPublishedUrl;
function getGalleryAPI(pat) {
return __awaiter(this, void 0, void 0, function* () {
// from https://github.com/Microsoft/tfs-cli/blob/master/app/exec/extension/default.ts#L287-L292
const authHandler = WebApi_1.getBasicHandler('OAuth', pat);
return new GalleryApi_1.GalleryApi(marketplaceUrl, [authHandler]);
// const vsoapi = new WebApi(marketplaceUrl, authHandler);
// return await vsoapi.getGalleryApi();
});
}
exports.getGalleryAPI = getGalleryAPI;
function getSecurityRolesAPI(pat) {
return __awaiter(this, void 0, void 0, function* () {
const authHandler = WebApi_1.getBasicHandler('OAuth', pat);
const vsoapi = new WebApi_1.WebApi(marketplaceUrl, authHandler);
return yield vsoapi.getSecurityRolesApi();
});
}
exports.getSecurityRolesAPI = getSecurityRolesAPI;
function getPublicGalleryAPI() {
return new publicgalleryapi_1.PublicGalleryAPI(marketplaceUrl, '3.0-preview.1');
}
exports.getPublicGalleryAPI = getPublicGalleryAPI;
function normalize(path) {
return path.replace(/\\/g, '/');
}
exports.normalize = normalize;
function chain2(a, b, fn, index = 0) {
if (index >= b.length) {
return Promise.resolve(a);
}
return fn(a, b[index]).then(a => chain2(a, b, fn, index + 1));
}
function chain(initial, processors, process) {
return chain2(initial, processors, process);
}
exports.chain = chain;
function flatten(arr) {
return [].concat.apply([], arr);
}
exports.flatten = flatten;
const CancelledError = 'Cancelled';
function isCancelledError(error) {
return error === CancelledError;
}
exports.isCancelledError = isCancelledError;
class CancellationToken {
constructor() {
this.listeners = [];
this._cancelled = false;
}
get isCancelled() { return this._cancelled; }
subscribe(fn) {
this.listeners.push(fn);
return () => {
const index = this.listeners.indexOf(fn);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
cancel() {
const emit = !this._cancelled;
this._cancelled = true;
if (emit) {
this.listeners.forEach(l => l(CancelledError));
this.listeners = [];
}
}
}
exports.CancellationToken = CancellationToken;
function sequence(promiseFactories) {
return __awaiter(this, void 0, void 0, function* () {
for (const factory of promiseFactories) {
yield factory();
}
});
}
exports.sequence = sequence;
var LogMessageType;
(function (LogMessageType) {
LogMessageType[LogMessageType["DONE"] = 0] = "DONE";
LogMessageType[LogMessageType["INFO"] = 1] = "INFO";
LogMessageType[LogMessageType["WARNING"] = 2] = "WARNING";
LogMessageType[LogMessageType["ERROR"] = 3] = "ERROR";
})(LogMessageType || (LogMessageType = {}));
const LogPrefix = {
[LogMessageType.DONE]: chalk_1.default.bgGreen.black(' DONE '),
[LogMessageType.INFO]: chalk_1.default.bgBlueBright.black(' INFO '),
[LogMessageType.WARNING]: chalk_1.default.bgYellow.black(' WARNING '),
[LogMessageType.ERROR]: chalk_1.default.bgRed.black(' ERROR '),
};
function _log(type, msg, ...args) {
args = [LogPrefix[type], msg, ...args];
if (type === LogMessageType.WARNING) {
console.warn(...args);
}
else if (type === LogMessageType.ERROR) {
console.error(...args);
}
else {
console.log(...args);
}
}
exports.log = {
done: _log.bind(null, LogMessageType.DONE),
info: _log.bind(null, LogMessageType.INFO),
warn: _log.bind(null, LogMessageType.WARNING),
error: _log.bind(null, LogMessageType.ERROR)
};
+96
View File
@@ -0,0 +1,96 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const semver = require("semver");
const parseSemver = require("parse-semver");
const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i;
function validatePublisher(publisher) {
if (!publisher) {
throw new Error(`Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions`);
}
if (!nameRegex.test(publisher)) {
throw new Error(`Invalid publisher name '${publisher}'. Expected the identifier of a publisher, not its human-friendly name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions`);
}
}
exports.validatePublisher = validatePublisher;
function validateExtensionName(name) {
if (!name) {
throw new Error(`Missing extension name`);
}
if (!nameRegex.test(name)) {
throw new Error(`Invalid extension name '${name}'`);
}
}
exports.validateExtensionName = validateExtensionName;
function validateVersion(version) {
if (!version) {
throw new Error(`Missing extension version`);
}
if (!semver.valid(version)) {
throw new Error(`Invalid extension version '${version}'`);
}
}
exports.validateVersion = validateVersion;
function validateEngineCompatibility(version) {
if (!version) {
throw new Error(`Missing vscode engine compatibility version`);
}
if (!/^\*$|^(\^|>=)?((\d+)|x)\.((\d+)|x)\.((\d+)|x)(\-.*)?$/.test(version)) {
throw new Error(`Invalid vscode engine compatibility version '${version}'`);
}
}
exports.validateEngineCompatibility = validateEngineCompatibility;
/**
* User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode
*/
function validateVSCodeTypesCompatibility(engineVersion, typeVersion) {
if (engineVersion === '*') {
return;
}
if (!typeVersion) {
throw new Error(`Missing @types/vscode version`);
}
let plainEngineVersion, plainTypeVersion;
try {
const engineSemver = parseSemver(`vscode@${engineVersion}`);
plainEngineVersion = engineSemver.version;
}
catch (err) {
throw new Error('Failed to parse semver of engines.vscode');
}
try {
const typeSemver = parseSemver(`@types/vscode@${typeVersion}`);
plainTypeVersion = typeSemver.version;
}
catch (err) {
throw new Error('Failed to parse semver of @types/vscode');
}
// For all `x`, use smallest version for comparison
plainEngineVersion = plainEngineVersion.replace(/x/g, '0');
const [typeMajor, typeMinor, typePatch] = plainTypeVersion.split('.').map(x => {
try {
return parseInt(x);
}
catch (err) {
return 0;
}
});
const [engineMajor, engineMinor, enginePatch] = plainEngineVersion.split('.').map(x => {
try {
return parseInt(x);
}
catch (err) {
return 0;
}
});
const error = new Error(`@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Consider upgrade engines.vscode or use an older @types/vscode version`);
if (typeof typeMajor === 'number' && typeof engineMajor === 'number' && typeMajor > engineMajor) {
throw error;
}
if (typeof typeMinor === 'number' && typeof engineMinor === 'number' && typeMinor > engineMinor) {
throw error;
}
if (typeof typePatch === 'number' && typeof enginePatch === 'number' && typePatch > enginePatch) {
throw error;
}
}
exports.validateVSCodeTypesCompatibility = validateVSCodeTypesCompatibility;
+73
View File
@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fixedLocale = 'en-us';
const format = {
date: { month: 'long', day: 'numeric', year: 'numeric' },
time: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
};
const columns = process.stdout.columns ? process.stdout.columns : 80;
// xxx: Windows cmd + powershell standard fonts currently don't support the full
// unicode charset. For now we use fallback icons when on windows.
const useFallbackIcons = process.platform === 'win32';
exports.icons = useFallbackIcons ?
{
download: '\u{2193}',
star: '\u{2665}',
emptyStar: '\u{2022}',
} : {
download: '\u{2913}',
star: '\u{2605}',
emptyStar: '\u{2606}',
};
function formatDate(date) { return date.toLocaleString(fixedLocale, format.date); }
exports.formatDate = formatDate;
function formatTime(date) { return date.toLocaleString(fixedLocale, format.time); }
exports.formatTime = formatTime;
function formatDateTime(date) { return date.toLocaleString(fixedLocale, Object.assign({}, format.date, format.time)); }
exports.formatDateTime = formatDateTime;
function repeatString(text, count) {
let result = '';
for (let i = 0; i < count; i++) {
result += text;
}
return result;
}
exports.repeatString = repeatString;
function ratingStars(rating, total = 5) {
const c = Math.min(Math.round(rating), total);
return `${repeatString(exports.icons.star + ' ', c)}${repeatString(exports.icons.emptyStar + ' ', total - c)}`;
}
exports.ratingStars = ratingStars;
function tableView(table, spacing = 2) {
const maxLen = {};
table.forEach(row => row.forEach((cell, i) => maxLen[i] = Math.max(maxLen[i] || 0, cell.length)));
return table.map(row => row.map((cell, i) => `${cell}${repeatString(' ', maxLen[i] - cell.length + spacing)}`).join(''));
}
exports.tableView = tableView;
function wordWrap(text, width = columns) {
const [indent = ''] = text.match(/^\s+/) || [];
const maxWidth = width - indent.length;
return text
.replace(/^\s+/, '')
.split('')
.reduce(([out, buffer, pos], ch, i) => {
const nl = pos === maxWidth ? `\n${indent}` : '';
const newPos = nl ? 0 : +pos + 1;
return / |-|,|\./.test(ch) ?
[`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer + ch, newPos];
}, [indent, '', 0])
.slice(0, 2)
.join('');
}
exports.wordWrap = wordWrap;
;
function indentRow(row) { return ` ${row}`; }
exports.indentRow = indentRow;
;
function wordTrim(text, width = columns, indicator = '...') {
if (text.length > width) {
return text.substr(0, width - indicator.length) + indicator;
}
return text;
}
exports.wordTrim = wordTrim;
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./main')(process.argv);
+81
View File
@@ -0,0 +1,81 @@
{
"name": "vsce",
"version": "1.66.0",
"description": "VSCode Extension Manager",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vsce"
},
"homepage": "https://code.visualstudio.com",
"bugs": "https://github.com/Microsoft/vsce/issues",
"keywords": [
"vscode",
"vsce",
"extension"
],
"contributors": [
"Microsoft Corporation"
],
"author": "Microsoft Corporation",
"license": "MIT",
"main": "out/api.js",
"typings": "out/api.d.ts",
"bin": {
"vsce": "out/vsce"
},
"scripts": {
"compile": "tsc && cpx src/vsce out",
"watch": "concurrently \"tsc --watch\" \"cpx --watch src/vsce out\"",
"watch-test": "concurrently \"tsc --watch\" \"cpx --watch src/vsce out\" \"mocha --watch\"",
"test": "mocha",
"prepublishOnly": "tsc && cpx src/vsce out && mocha",
"vsce": "out/vsce"
},
"engines": {
"node": ">= 8"
},
"dependencies": {
"azure-devops-node-api": "^7.2.0",
"chalk": "^2.4.2",
"cheerio": "^1.0.0-rc.1",
"commander": "^2.8.1",
"denodeify": "^1.2.1",
"didyoumean": "^1.2.1",
"glob": "^7.0.6",
"lodash": "^4.17.10",
"markdown-it": "^8.3.1",
"mime": "^1.3.4",
"minimatch": "^3.0.3",
"osenv": "^0.1.3",
"parse-semver": "^1.1.1",
"read": "^1.0.7",
"semver": "^5.1.0",
"tmp": "0.0.29",
"typed-rest-client": "1.2.0",
"url-join": "^1.1.0",
"yauzl": "^2.3.1",
"yazl": "^2.2.2"
},
"devDependencies": {
"@types/cheerio": "^0.22.1",
"@types/denodeify": "^1.2.31",
"@types/didyoumean": "^1.2.0",
"@types/glob": "^7.1.1",
"@types/lodash": "^4.14.123",
"@types/markdown-it": "0.0.2",
"@types/mime": "^1",
"@types/minimatch": "^3.0.3",
"@types/mocha": "^5.2.6",
"@types/node": "^8",
"@types/read": "^0.0.28",
"@types/semver": "^6.0.0",
"@types/tmp": "^0.1.0",
"@types/xml2js": "^0.4.4",
"concurrently": "^4.1.0",
"cpx": "^1.5.0",
"mocha": "^5.2.0",
"source-map-support": "^0.4.2",
"typescript": "^3.4.3",
"xml2js": "^0.4.12"
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<% _.forEach(contentTypes, function (e) { %><Default Extension="${ e.extension }" ContentType="${ e.contentType }"/><% }); %>
</Types>
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Language="en-US" Id="<%- id %>" Version="<%- version %>" Publisher="<%- publisher %>"/>
<DisplayName><%- displayName %></DisplayName>
<Description xml:space="preserve"><%- description %></Description>
<Tags><%- tags %></Tags>
<Categories><%- categories %></Categories>
<GalleryFlags><%- flags %></GalleryFlags>
<Badges><% _.forEach(badges, function (badge) { %><Badge Link="<%- badge.href %>" ImgUri="<%- badge.url %>" Description="<%- badge.description %>" /><% }); %></Badges>
<Properties>
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="<%- engine %>" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="<%- extensionDependencies %>" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="<%- extensionPack %>" />
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="<%- localizedLanguages %>" />
<% if (links.repository) { %>
<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="<%- links.repository %>" />
<Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="<%- links.repository %>" />
<% if (links.github) { %>
<Property Id="Microsoft.VisualStudio.Services.Links.GitHub" Value="<%- links.github %>" />
<% } else { %>
<Property Id="Microsoft.VisualStudio.Services.Links.Repository" Value="<%- links.repository %>" />
<% } %>
<% } %>
<% if (links.bugs) { %><Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="<%- links.bugs %>" /><% } %>
<% if (links.homepage) { %><Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="<%- links.homepage %>" /><% } %>
<% if (galleryBanner.color) { %><Property Id="Microsoft.VisualStudio.Services.Branding.Color" Value="<%- galleryBanner.color %>" /><% } %>
<% if (galleryBanner.theme) { %><Property Id="Microsoft.VisualStudio.Services.Branding.Theme" Value="<%- galleryBanner.theme %>" /><% } %>
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="<%- githubMarkdown %>" />
<% if (typeof enableMarketplaceQnA === 'boolean') { %><Property Id="Microsoft.VisualStudio.Services.EnableMarketplaceQnA" Value="<%- enableMarketplaceQnA %>" /><% } %>
<% if (customerQnALink) { %><Property Id="Microsoft.VisualStudio.Services.CustomerQnALink" Value="<%- customerQnALink %>" /><% } %>
</Properties>
<% if (license) { %><License><%- license %></License><% } %>
<% if (icon) { %><Icon><%- icon %></Icon><% } %>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Code"/>
</Installation>
<Dependencies/>
<Assets>
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
<% _.forEach(assets, function (asset) { %><Asset Type="<%- asset.type %>" Path="<%- asset.path %>" Addressable="true" /><% }); %>
</Assets>
</PackageManifest>