From e13aaced360679508921a5a70440a1f18c234299 Mon Sep 17 00:00:00 2001 From: Sebastiaan Brouwer Date: Tue, 29 Sep 2020 10:29:49 +0200 Subject: [PATCH] Merge master-branch --- .prettierignore | 3 + .vscode/extensions.json | 6 +- .vscode/launch.json | 7 +- .vscode/settings.json | 2 +- .vscode/tasks.json | 8 +- README.md | 6 +- azure-pipelines.yml | 38 +- package.json | 181 +-- resources/extension.vsixmanifest | 2 + src/api.ts | 14 +- src/main.ts | 108 +- src/manifest.ts | 23 +- src/nls.ts | 2 +- src/npm.ts | 48 +- src/package.ts | 517 +++++-- src/publicgalleryapi.ts | 45 +- src/publish.ts | 59 +- src/search.ts | 35 +- src/show.ts | 90 +- src/store.ts | 63 +- .../readme/readme.branch.main.expected.md | 48 + ...readme.branch.override.content.expected.md | 48 + .../readme.branch.override.images.expected.md | 48 + src/test/fixtures/readme/readme.expected.md | 1 + .../fixtures/readme/readme.github.expected.md | 1 + src/test/fixtures/readme/readme.github.md | 1 + .../fixtures/readme/readme.images.expected.md | 1 + src/test/fixtures/readme/readme.md | 1 + src/test/package.test.ts | 1312 ++++++++++++----- src/test/validation.test.ts | 11 +- src/util.ts | 11 +- src/validation.ts | 16 +- src/viewutils.ts | 42 +- tsconfig.json | 6 +- yarn.lock | 448 +++++- 35 files changed, 2400 insertions(+), 852 deletions(-) create mode 100644 .prettierignore create mode 100644 src/test/fixtures/readme/readme.branch.main.expected.md create mode 100644 src/test/fixtures/readme/readme.branch.override.content.expected.md create mode 100644 src/test/fixtures/readme/readme.branch.override.images.expected.md diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..33a58293f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +out +resources +fixtures \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index cb0065213..78bd74f6f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,9 +2,7 @@ // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "hbenl.vscode-mocha-test-adapter", - ], + "recommendations": ["hbenl.vscode-mocha-test-adapter"], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] -} \ No newline at end of file +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 5e421d835..15bf3d127 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,13 +8,14 @@ "type": "node", "request": "launch", "name": "Launch Program", + // "cwd": "", "program": "${workspaceFolder}/out/vsce", "args": [ "--version" + // "ls", "package", "publish" ], "sourceMaps": true, - "outputCapture": "std", - "preLaunchTask": "compile" + "outputCapture": "std" } ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 540c24fa2..be2730119 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,4 +7,4 @@ }, "typescript.tsdk": "./node_modules/typescript/lib", "mochaExplorer.files": "out/**/test/*.test.js" -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f49ea1991..6f6d84e21 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,14 +1,12 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 + // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type": "npm", "script": "watch", - "problemMatcher": [ - "$tsc-watch" - ], + "problemMatcher": ["$tsc-watch"], "group": { "kind": "build", "isDefault": true @@ -20,4 +18,4 @@ "script": "test" } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index 03d2bdfce..b287d73a0 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # vsce -> *The Visual Studio Code Extension Manager* +> _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` +- [Node.js](https://nodejs.org/en/) at least `10.x.x` ## Usage `vsce` is meant to be mainly used as a command line tool. It can also be used a library since it exposes a small [API](https://github.com/microsoft/vscode-vsce/blob/master/src/api.ts). -> **Warning:** When using vsce as a library be sure to sanitize any user input used in API calls, as a security measurement. +> **Warning:** When using vsce as a library be sure to sanitize any user input used in API calls, as a security measure. ## Development diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ad3251e41..d61c81199 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,27 +5,27 @@ trigger: include: ['*'] steps: -- task: NodeTool@0 - inputs: - versionSpec: "8.x" + - task: NodeTool@0 + inputs: + versionSpec: '10.x' -- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 - inputs: - versionSpec: "1.x" + - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: '1.x' -- script: yarn - displayName: Install Dependencies + - script: yarn + displayName: Install Dependencies -- script: yarn compile - displayName: Compile + - script: yarn compile + displayName: Compile -- script: yarn test - displayName: Run Tests + - 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/')) \ No newline at end of file + - task: Npm@1 + displayName: 'Publish to NPM' + inputs: + command: publish + verbose: false + publishEndpoint: 'NPM' + condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/')) diff --git a/package.json b/package.json index 4958ca533..06ba25978 100644 --- a/package.json +++ b/package.json @@ -1,86 +1,101 @@ { - "name": "vsce", - "version": "1.77.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 && cp src/vsce out/vsce", - "watch": "cp src/vsce out/vsce && tsc --watch", - "watch-test": "cp src/vsce out/vsce && concurrently \"tsc --watch\" \"mocha --watch\"", - "test": "mocha", - "prepublishOnly": "tsc && cp src/vsce out/vsce && 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", - "find-yarn-workspace-root": "^2.0.0", + "name": "vsce", + "version": "1.80.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": { + "copy-vsce": "mkdir -p out && cp src/vsce out/vsce", + "compile": "tsc && yarn copy-vsce", + "watch": "yarn copy-vsce && tsc --watch", + "watch-test": "yarn copy-vsce && concurrently \"tsc --watch\" \"mocha --watch\"", + "test": "mocha", + "prepublishOnly": "tsc && yarn copy-vsce && mocha", + "vsce": "out/vsce" + }, + "engines": { + "node": ">= 10" + }, + "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", + "find-yarn-workspace-root": "^2.0.0", "glob": "^7.0.6", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "markdown-it": "^10.0.0", - "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/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": "^7.0.2", - "@types/node": "^8", - "@types/read": "^0.0.28", - "@types/semver": "^6.0.0", - "@types/tmp": "^0.1.0", - "@types/xml2js": "^0.4.4", - "concurrently": "^5.1.0", - "mocha": "^7.1.1", - "source-map-support": "^0.4.2", - "typescript": "^3.4.3", - "xml2js": "^0.4.12" - }, - "mocha": { - "require": [ - "source-map-support/register" - ], - "spec": "out/test" - } + "leven": "^3.1.0", + "lodash": "^4.17.15", + "markdown-it": "^10.0.0", + "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/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": "^7.0.2", + "@types/node": "^8", + "@types/read": "^0.0.28", + "@types/semver": "^6.0.0", + "@types/tmp": "^0.1.0", + "@types/xml2js": "^0.4.4", + "concurrently": "^5.1.0", + "husky": "^4.3.0", + "mocha": "^7.1.1", + "prettier": "2.1.2", + "pretty-quick": "^3.0.2", + "source-map-support": "^0.4.2", + "typescript": "^3.4.3", + "xml2js": "^0.4.12" + }, + "mocha": { + "require": [ + "source-map-support/register" + ], + "spec": "out/test" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } + }, + "prettier": { + "useTabs": true, + "printWidth": 120, + "singleQuote": true, + "arrowParens": "avoid" + } } diff --git a/resources/extension.vsixmanifest b/resources/extension.vsixmanifest index 0fd2ee2d3..a110f72d2 100644 --- a/resources/extension.vsixmanifest +++ b/resources/extension.vsixmanifest @@ -12,6 +12,7 @@ + <% if (links.repository) { %> @@ -29,6 +30,7 @@ <% if (typeof enableMarketplaceQnA === 'boolean') { %><% } %> <% if (customerQnALink) { %><% } %> + <% if (typeof webExtension === 'boolean') { %><% } %> <% if (license) { %><%- license %><% } %> <% if (icon) { %><%- icon %><% } %> diff --git a/src/api.ts b/src/api.ts index aa3f3c5d8..25ffd7cd7 100644 --- a/src/api.ts +++ b/src/api.ts @@ -33,7 +33,6 @@ export interface ICreateVSIXOptions { } export interface IPublishOptions { - /** * The location of the extension in the file system. * @@ -69,11 +68,10 @@ export interface IPublishOptions { */ export enum PackageManager { Npm, - Yarn + Yarn, } export interface IListFilesOptions { - /** * The working directory of the extension. Defaults to `process.cwd()`. */ @@ -85,7 +83,7 @@ export interface IListFilesOptions { packageManager?: PackageManager; /** - * A subset of the top level dependencies which should be included. The + * 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. */ @@ -100,7 +98,6 @@ export interface IListFilesOptions { } export interface IPublishVSIXOptions { - /** * The Personal Access Token to use. * @@ -142,7 +139,12 @@ export function publish(options: IPublishOptions = {}): Promise { * Lists the files included in the extension's package. */ export function listFiles(options: IListFilesOptions = {}): Promise { - return _listFiles(options.cwd, options.packageManager === PackageManager.Yarn, options.packagedDependencies, options.ignoreFile); + return _listFiles( + options.cwd, + options.packageManager === PackageManager.Yarn, + options.packagedDependencies, + options.ignoreFile + ); } /** diff --git a/src/main.ts b/src/main.ts index 0872420d9..1d2ff991b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -38,44 +38,72 @@ function main(task: Promise): void { if (isatty(1)) { getLatestVersion(pkg.name, token) - .then(version => latestVersion = version) - .catch(_ => { /* noop */ }); + .then(version => (latestVersion = version)) + .catch(_ => { + /* noop */ + }); } - task - .catch(fatal) - .then(() => { - if (latestVersion && semver.gt(latestVersion, pkg.version)) { - 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(); - } - }); + task.catch(fatal).then(() => { + if (latestVersion && semver.gt(latestVersion, pkg.version)) { + 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: string[]): void { - program - .version(pkg.version) - .usage(' [options]'); + program.version(pkg.version).usage(' [options]'); program .command('ls') .description('Lists all the files that will be published') .option('--yarn', 'Use yarn instead of npm') - .option('--packagedDependencies ', 'Select packages that should be published only (includes dependencies)', (val, all) => all ? all.concat(val) : [val], undefined) + .option( + '--packagedDependencies ', + 'Select packages that should be published only (includes dependencies)', + (val, all) => (all ? all.concat(val) : [val]), + undefined + ) .option('--ignoreFile [path]', 'Indicate alternative .vscodeignore') - .action(({ yarn, packagedDependencies, ignoreFile }) => main(ls(undefined, yarn, packagedDependencies, ignoreFile))); + .action(({ yarn, packagedDependencies, ignoreFile }) => + main(ls(undefined, yarn, packagedDependencies, ignoreFile)) + ); program .command('package') .description('Packages an extension') .option('-o, --out [path]', 'Output .vsix extension file to [path] location') + .option( + '--githubBranch [branch]', + 'The GitHub branch used to infer relative links in README.md. Can be overriden by --baseContentUrl and --baseImagesUrl.' + ) .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') .option('--ignoreFile [path]', 'Indicate alternative .vscodeignore') .option('--noGitHubIssueLinking', 'Prevent automatic expansion of GitHub-style issue syntax into links') - .action(({ out, baseContentUrl, baseImagesUrl, yarn, ignoreFile, noGitHubIssueLinking }) => main(packageCommand({ packagePath: out, baseContentUrl, baseImagesUrl, useYarn: yarn, ignoreFile, expandGitHubIssueLinks: noGitHubIssueLinking }))); + .option( + '--web', + 'Experimental flag to enable publishing web extensions. Note: This is supported only for selected extensions.' + ) + .action(({ out, githubBranch, baseContentUrl, baseImagesUrl, yarn, ignoreFile, noGitHubIssueLinking, web }) => + main( + packageCommand({ + packagePath: out, + githubBranch, + baseContentUrl, + baseImagesUrl, + useYarn: yarn, + ignoreFile, + expandGitHubIssueLinks: noGitHubIssueLinking, + web + }) + ) + ); program .command('publish []') @@ -83,12 +111,40 @@ module.exports = function (argv: string[]): void { .option('-p, --pat ', 'Personal Access Token', process.env['VSCE_PAT']) .option('-m, --message ', 'Commit message used when calling `npm version`.') .option('--packagePath [path]', 'Publish the VSIX package located at the specified path.') + .option( + '--githubBranch [branch]', + 'The GitHub branch used to infer relative links in README.md. Can be overriden by --baseContentUrl and --baseImagesUrl.' + ) .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') .option('--ignoreFile [path]', 'Indicate alternative .vscodeignore') - .action((version, { pat, message, packagePath, baseContentUrl, baseImagesUrl, yarn, noVerify, ignoreFile }) => main(publish({ pat, commitMessage: message, version, packagePath, baseContentUrl, baseImagesUrl, useYarn: yarn, noVerify, ignoreFile }))); + .option( + '--web', + 'Experimental flag to enable publishing web extensions. Note: This is supported only for selected extensions.' + ) + .action( + ( + version, + { pat, message, packagePath, githubBranch, baseContentUrl, baseImagesUrl, yarn, noVerify, ignoreFile, web } + ) => + main( + publish({ + pat, + commitMessage: message, + version, + packagePath, + githubBranch, + baseContentUrl, + baseImagesUrl, + useYarn: yarn, + noVerify, + ignoreFile, + web, + }) + ) + ); program .command('unpublish []') @@ -134,19 +190,17 @@ module.exports = function (argv: string[]): void { .description('search extension gallery') .action((text, { json }) => main(search(text, json))); - program - .command('*', '', { noHelp: true }) - .action((cmd: string) => { - program.help(help => { - const availableCommands = program.commands.map(c => c._name); - const suggestion = availableCommands.find(c => leven(c, cmd) < c.length * 0.4); + program.command('*', '', { noHelp: true }).action((cmd: string) => { + program.help(help => { + const availableCommands = program.commands.map(c => c._name); + const suggestion = availableCommands.find(c => leven(c, cmd) < c.length * 0.4); - help = `${help} + help = `${help} Unknown command '${cmd}'`; - return suggestion ? `${help}, did you mean '${suggestion}'?\n` : `${help}.\n`; - }); + return suggestion ? `${help}, did you mean '${suggestion}'?\n` : `${help}.\n`; }); + }); program.parse(argv); diff --git a/src/manifest.ts b/src/manifest.ts index 542781aab..368598ec7 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -17,15 +17,17 @@ export interface Localization { } export interface Contributions { - 'localizations'?: Localization[]; + localizations?: Localization[]; [contributionType: string]: any; } +export type ExtensionKind = 'ui' | 'workspace' | 'web'; + export interface Manifest { // mandatory (npm) name: string; version: string; - engines: { [name: string]: string; }; + engines: { [name: string]: string }; // vscode publisher: string; @@ -34,15 +36,15 @@ export interface Manifest { activationEvents?: string[]; extensionDependencies?: string[]; extensionPack?: string[]; - galleryBanner?: { color?: string; theme?: string; }; + galleryBanner?: { color?: string; theme?: string }; preview?: boolean; - badges?: { url: string; href: string; description: string; }[]; + badges?: { url: string; href: string; description: string }[]; markdown?: 'github' | 'standard'; - _bundling?: { [name: string]: string; }[]; + _bundling?: { [name: string]: string }[]; _testing?: string; enableProposedApi?: boolean; qna?: 'marketplace' | string | false; - extensionKind?: string[]; + extensionKind?: ExtensionKind | ExtensionKind[]; // optional (npm) author?: string | Person; @@ -55,10 +57,11 @@ export interface Manifest { license?: string; contributors?: string | Person[]; main?: string; - repository?: string | { type?: string; url?: string; }; - scripts?: { [name: string]: string; }; - dependencies?: { [name: string]: string; }; - devDependencies?: { [name: string]: string; }; + browser?: string; + repository?: string | { type?: string; url?: string }; + scripts?: { [name: string]: string }; + dependencies?: { [name: string]: string }; + devDependencies?: { [name: string]: string }; private?: boolean; // not supported (npm) diff --git a/src/nls.ts b/src/nls.ts index 2ed36b980..01f40bd5c 100644 --- a/src/nls.ts +++ b/src/nls.ts @@ -25,4 +25,4 @@ function patcher(translations: ITranslations) { export function patchNLS(manifest: Manifest, translations: ITranslations): Manifest { return cloneDeepWith(manifest, patcher(translations)) as Manifest; -} \ No newline at end of file +} diff --git a/src/npm.ts b/src/npm.ts index 9200a755c..4f60ae1e3 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -21,7 +21,11 @@ function parseStdout({ stdout }: { stdout: string }): string { return stdout.split(/[\r\n]/).filter(line => !!line)[0]; } -function exec(command: string, options: IOptions = {}, cancellationToken?: CancellationToken): Promise<{ stdout: string; stderr: string; }> { +function exec( + command: string, + options: IOptions = {}, + cancellationToken?: CancellationToken +): Promise<{ stdout: string; stderr: string }> { return new Promise((c, e) => { let disposeCancellationListener: Function = null; @@ -31,7 +35,9 @@ function exec(command: string, options: IOptions = {}, cancellationToken?: Cance disposeCancellationListener = null; } - if (err) { return e(err); } + if (err) { + return e(err); + } c({ stdout, stderr }); }); @@ -56,14 +62,14 @@ function checkNPM(cancellationToken?: CancellationToken): Promise { function getNpmDependencies(cwd: string): Promise { return checkNPM() - .then(() => exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 })) - .then(({ stdout }) => stdout - .split(/[\r\n]/) - .filter(dir => path.isAbsolute(dir)) + .then(() => + exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 }) + ) + .then(({ stdout }) => stdout.split(/[\r\n]/).filter(dir => path.isAbsolute(dir)) .map(dir => { - return { - src: dir, - dest: path.relative(cwd, dir) + return { + src: dir, + dest: path.relative(cwd, dir) } })); } @@ -96,10 +102,10 @@ async function asYarnDependencies(root: string, rootDependencies: string[]): Pro } } const children = await resolve(depPath, Object.keys(depManifest.dependencies || {})); - return { - name, + return { + name, path: { - src: depPath, + src: depPath, dest: path.relative(root, depPath), }, children, @@ -109,8 +115,7 @@ async function asYarnDependencies(root: string, rootDependencies: string[]): Pro } function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: string[]): YarnDependency[] { - - const index = new class { + const index = new (class { private data: { [name: string]: YarnDependency } = Object.create(null); constructor() { for (const dep of deps) { @@ -127,9 +132,9 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st } return result; } - }; + })(); - const reached = new class { + const reached = new (class { values: YarnDependency[] = []; add(dep: YarnDependency): boolean { if (this.values.indexOf(dep) < 0) { @@ -138,7 +143,7 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st } return false; } - }; + })(); const visit = (name: string) => { let dep = index.find(name); @@ -168,14 +173,17 @@ async function getYarnProductionDependencies(root: string, manifest: Manifest, p async function getYarnDependencies(cwd: string, manifest: Manifest, packagedDependencies?: string[]): Promise { const root = findWorkspaceRoot(cwd) || cwd; - const result: SourceAndDestination[] = [{ - src: cwd, + const result: SourceAndDestination[] = [{ + src: cwd, dest: '' }]; if (await new Promise(c => fs.exists(path.join(root, 'yarn.lock'), c))) { const deps = await getYarnProductionDependencies(root, manifest, packagedDependencies); - const flatten = (dep: YarnDependency) => { result.push(dep.path); dep.children.forEach(flatten); }; + const flatten = (dep: YarnDependency) => { + result.push(dep.path); + dep.children.forEach(flatten); + }; deps.forEach(flatten); } diff --git a/src/package.ts b/src/package.ts index d9c2f49c7..3b1e44efa 100644 --- a/src/package.ts +++ b/src/package.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import * as cp from 'child_process'; import * as _ from 'lodash'; import * as yazl from 'yazl'; -import { Manifest } from './manifest'; +import { ExtensionKind, Manifest } from './manifest'; import { ITranslations, patchNLS } from './nls'; import * as util from './util'; import * as _glob from 'glob'; @@ -14,8 +14,15 @@ import * as cheerio from 'cheerio'; import * as url from 'url'; import { lookup } from 'mime'; import * as urljoin from 'url-join'; -import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from './validation'; +import { + validatePublisher, + validateExtensionName, + validateVersion, + validateEngineCompatibility, + validateVSCodeTypesCompatibility, +} from './validation'; import { getDependencies, SourceAndDestination } from './npm'; +import { IExtensionsReport } from './publicgalleryapi'; const readFile = denodeify(fs.readFile); const unlink = denodeify(fs.unlink as any); @@ -28,15 +35,25 @@ const contentTypesTemplatePath = path.join(resourcesPath, '[Content_Types].xml') const MinimatchOptions: minimatch.IOptions = { dot: true }; -export interface IFile { +export interface IInMemoryFile { path: string; - contents?: Buffer | string; - localPath?: string; + readonly contents: Buffer | string; +} + +export interface ILocalFile { + path: string; + readonly localPath: string; +} + +export type IFile = IInMemoryFile | ILocalFile; + +function isInMemoryFile(file: IFile): file is IInMemoryFile { + return !!(file as IInMemoryFile).contents; } export function read(file: IFile): Promise { - if (file.contents) { - return Promise.resolve(file.contents).then(b => typeof b === 'string' ? b : b.toString('utf8')); + if (isInMemoryFile(file)) { + return Promise.resolve(file.contents).then(b => (typeof b === 'string' ? b : b.toString('utf8'))); } else { return readFile(file.localPath, 'utf8'); } @@ -59,30 +76,38 @@ export interface IAsset { export interface IPackageOptions { cwd?: string; packagePath?: string; + githubBranch?: string; baseContentUrl?: string; baseImagesUrl?: string; useYarn?: boolean; dependencyEntryPoints?: string[]; ignoreFile?: string; expandGitHubIssueLinks?: boolean; + web?: boolean; } export interface IProcessor { onFile(file: IFile): Promise; onEnd(): Promise; assets: IAsset[]; + tags: string[]; vsix: any; } export class BaseProcessor implements IProcessor { - constructor(protected manifest: Manifest) { } + constructor(protected manifest: Manifest) {} assets: IAsset[] = []; + tags: string[] = []; vsix: any = Object.create(null); - onFile(file: IFile): Promise { return Promise.resolve(file); } - onEnd() { return Promise.resolve(null); } + onFile(file: IFile): Promise { + return Promise.resolve(file); + } + onEnd() { + return Promise.resolve(null); + } } -function getUrl(url: string | { url?: string; }): string { +function getUrl(url: string | { url?: string }): string { if (!url) { return null; } @@ -94,7 +119,7 @@ function getUrl(url: string | { url?: string; }): string { return (url).url; } -function getRepositoryUrl(url: string | { url?: string; }): string { +function getRepositoryUrl(url: string | { url?: string }): string { const result = getUrl(url); if (/^[^\/]+\/[^\/]+$/.test(result)) { @@ -107,7 +132,7 @@ function getRepositoryUrl(url: string | { url?: string; }): string { // 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 + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } function toExtensionTags(extensions: string[]): string[] { @@ -168,7 +193,7 @@ const TrustedSVGSources = [ 'visualstudio.com', 'vsmarketplacebadge.apphb.com', 'www.bithound.io', - 'www.versioneye.com' + 'www.versioneye.com', ]; function isGitHubRepository(repository: string): boolean { @@ -184,7 +209,6 @@ function isHostTrusted(url: url.UrlWithStringQuery): boolean { } class ManifestProcessor extends BaseProcessor { - constructor(manifest: Manifest) { super(manifest); @@ -208,6 +232,8 @@ class ManifestProcessor extends BaseProcessor { enableMarketplaceQnA = false; } + const extensionKind = getExtensionKind(manifest); + this.vsix = { ...this.vsix, id: manifest.name, @@ -221,17 +247,26 @@ class ManifestProcessor extends BaseProcessor { links: { repository, bugs: getUrl(manifest.bugs), - homepage: manifest.homepage + 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(',') : '' + extensionDependencies: _(manifest.extensionDependencies || []) + .uniq() + .join(','), + extensionPack: _(manifest.extensionPack || []) + .uniq() + .join(','), + extensionKind: extensionKind.join(','), + localizedLanguages: + manifest.contributes && manifest.contributes.localizations + ? manifest.contributes.localizations + .map(loc => loc.localizedLanguageName || loc.languageName || loc.languageId) + .join(',') + : '', }; if (isGitHub) { @@ -241,11 +276,15 @@ class ManifestProcessor extends BaseProcessor { async onEnd(): Promise { if (typeof this.manifest.extensionKind === 'string') { - util.log.warn(`The 'extensionKind' property should be of type 'string[]'. Learn more at: https://aka.ms/vscode/api/incorrect-execution-location`); + util.log.warn( + `The 'extensionKind' property should be of type 'string[]'. Learn more at: https://aka.ms/vscode/api/incorrect-execution-location` + ); } 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.'); + 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) { @@ -259,44 +298,43 @@ class ManifestProcessor extends BaseProcessor { } export class TagsProcessor extends BaseProcessor { - private static Keywords = { - 'git': ['git'], - 'npm': ['node'], - 'spell': ['markdown'], - 'bootstrap': ['bootstrap'], - 'lint': ['linters'], - 'linting': ['linters'], - 'react': ['javascript'], - 'js': ['javascript'], - 'node': ['javascript', 'node'], + 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'], + 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'] + 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'], }; onEnd(): Promise { @@ -312,23 +350,31 @@ export class TagsProcessor extends BaseProcessor { 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 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 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 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 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, @@ -342,36 +388,42 @@ export class TagsProcessor extends BaseProcessor { ...languageContributions, ...languageActivations, ...grammars, - ...descriptionKeywords + ...descriptionKeywords, ]; - this.vsix.tags = _(tags) + this.tags = _(tags) .uniq() // deduplicate .compact() // remove falsey values - .join(','); + .value(); return Promise.resolve(null); } } export class MarkdownProcessor extends BaseProcessor { - private baseContentUrl: string; private baseImagesUrl: string; private isGitHub: boolean; private repositoryUrl: string; private expandGitHubIssueLinks: boolean; - constructor(manifest: Manifest, private name: string, private regexp: RegExp, private assetType: string, options: IPackageOptions = {}) { + constructor( + manifest: Manifest, + private name: string, + private regexp: RegExp, + private assetType: string, + options: IPackageOptions = {} + ) { super(manifest); - const guess = this.guessBaseUrls(); + const guess = this.guessBaseUrls(options.githubBranch); this.baseContentUrl = options.baseContentUrl || (guess && guess.content); this.baseImagesUrl = options.baseImagesUrl || options.baseContentUrl || (guess && guess.images); - this.repositoryUrl = (guess && guess.repository); + this.repositoryUrl = guess && guess.repository; this.isGitHub = isGitHubRepository(this.repositoryUrl); - this.expandGitHubIssueLinks = typeof options.expandGitHubIssueLinks === 'boolean' ? options.expandGitHubIssueLinks : true; + this.expandGitHubIssueLinks = + typeof options.expandGitHubIssueLinks === 'boolean' ? options.expandGitHubIssueLinks : true; } async onFile(file: IFile): Promise { @@ -390,14 +442,20 @@ export class MarkdownProcessor extends BaseProcessor { } const markdownPathRegex = /(!?)\[([^\]\[]*|!\[[^\]\[]*]\([^\)]+\))\]\(([^\)]+)\)/g; - const urlReplace = (_, isImage, title, link) => { + const urlReplace = (_, isImage, title, link: string) => { + if (/^mailto:/i.test(link)) { + return `${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.`); + 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.` + ); } } @@ -419,7 +477,9 @@ export class MarkdownProcessor extends BaseProcessor { const isLinkRelative = !/^\w+:\/\//.test(link) && link[0] !== '#'; if (!this.baseImagesUrl && isLinkRelative) { - throw new Error(`Couldn't detect the repository where this extension is published. The image will be broken in ${this.name}. Please provide the repository URL in package.json or use the --baseContentUrl and --baseImagesUrl options.`); + throw new Error( + `Couldn't detect the repository where this extension is published. The image will be broken in ${this.name}. Please provide the repository URL in package.json or use the --baseContentUrl and --baseImagesUrl options.` + ); } const prefix = this.baseImagesUrl; @@ -431,8 +491,13 @@ export class MarkdownProcessor extends BaseProcessor { }); if (this.isGitHub && this.expandGitHubIssueLinks) { - const markdownIssueRegex = /(\s|\n)([\w\d_-]+\/[\w\d_-]+)?#(\d+)\b/g - const issueReplace = (all: string, prefix: string, ownerAndRepositoryName: string, issueNumber: string): string => { + const markdownIssueRegex = /(\s|\n)([\w\d_-]+\/[\w\d_-]+)?#(\d+)\b/g; + const issueReplace = ( + all: string, + prefix: string, + ownerAndRepositoryName: string, + issueNumber: string + ): string => { let result = all; let owner: string; let repositoryName: string; @@ -445,14 +510,13 @@ export class MarkdownProcessor extends BaseProcessor { // 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); } @@ -472,8 +536,10 @@ export class MarkdownProcessor extends BaseProcessor { throw new Error(`Images in ${this.name} must come from an HTTPS source: ${src}`); } - if (/\.svg$/i.test(srcUrl.pathname) && (!isHostTrusted(srcUrl))) { - throw new Error(`SVGs are restricted in ${this.name}; please use other file image formats, such as PNG: ${src}`); + if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl)) { + throw new Error( + `SVGs are restricted in ${this.name}; please use other file image formats, such as PNG: ${src}` + ); } }); @@ -483,12 +549,12 @@ export class MarkdownProcessor extends BaseProcessor { return { path: file.path, - contents: Buffer.from(contents, 'utf8') + contents: Buffer.from(contents, 'utf8'), }; } // GitHub heuristics - private guessBaseUrls(): { content: string; images: string; repository: string } { + private guessBaseUrls(githubBranch: string | undefined): { content: string; images: string; repository: string } { let repository = null; if (typeof this.manifest.repository === 'string') { @@ -510,32 +576,36 @@ export class MarkdownProcessor extends BaseProcessor { const account = match[1]; const repositoryName = match[2].replace(/\.git$/i, ''); + const branchName = githubBranch ? githubBranch : 'master'; return { - content: `https://github.com/${account}/${repositoryName}/blob/master`, - images: `https://github.com/${account}/${repositoryName}/raw/master`, - repository: `https://github.com/${account}/${repositoryName}` + content: `https://github.com/${account}/${repositoryName}/blob/${branchName}`, + images: `https://github.com/${account}/${repositoryName}/raw/${branchName}`, + repository: `https://github.com/${account}/${repositoryName}`, }; } } export class ReadmeProcessor extends MarkdownProcessor { - constructor(manifest: Manifest, options: IPackageOptions = {}) { super(manifest, 'README.md', /^extension\/readme.md$/i, 'Microsoft.VisualStudio.Services.Content.Details', options); } } export class ChangelogProcessor extends MarkdownProcessor { - constructor(manifest: Manifest, options: IPackageOptions = {}) { - super(manifest, 'CHANGELOG.md', /^extension\/changelog.md$/i, 'Microsoft.VisualStudio.Services.Content.Changelog', options); + super( + manifest, + 'CHANGELOG.md', + /^extension\/changelog.md$/i, + 'Microsoft.VisualStudio.Services.Content.Changelog', + options + ); } } class LicenseProcessor extends BaseProcessor { - private didFindLicense = false; - private filter: (name: string) => boolean; + filter: (name: string) => boolean; constructor(manifest: Manifest) { super(manifest); @@ -573,7 +643,6 @@ class LicenseProcessor extends BaseProcessor { } class IconProcessor extends BaseProcessor { - private icon: string; private didFindIcon = false; @@ -603,14 +672,107 @@ class IconProcessor extends BaseProcessor { } } -export class NLSProcessor extends BaseProcessor { +export function isSupportedWebExtension(manifest: Manifest, extensionsReport: IExtensionsReport): boolean { + const id = `${manifest.publisher}.${manifest.name}`; + return ( + extensionsReport.web.publishers.some(publisher => manifest.publisher === publisher) || + extensionsReport.web.extensions.some(extension => extension === id) + ); +} + +export function isWebKind(manifest: Manifest): boolean { + const extensionKind = getExtensionKind(manifest); + return extensionKind.some(kind => kind === 'web'); +} + +const workspaceExtensionPoints: string[] = ['terminal', 'debuggers', 'jsonValidation']; + +function getExtensionKind(manifest: Manifest): ExtensionKind[] { + // check the manifest + if (manifest.extensionKind) { + return Array.isArray(manifest.extensionKind) + ? manifest.extensionKind + : manifest.extensionKind === 'ui' + ? ['ui', 'workspace'] + : [manifest.extensionKind]; + } + + // Not an UI extension if it has main + if (manifest.main) { + if (manifest.browser) { + return ['workspace', 'web']; + } + return ['workspace']; + } + + if (manifest.browser) { + return ['web']; + } + + const isNonEmptyArray = obj => Array.isArray(obj) && obj.length > 0; + // Not an UI nor web extension if it has dependencies or an extension pack + if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) { + return ['workspace']; + } + if (manifest.contributes) { + // Not an UI nor web extension if it has workspace contributions + for (const contribution of Object.keys(manifest.contributes)) { + if (workspaceExtensionPoints.indexOf(contribution) !== -1) { + return ['workspace']; + } + } + } + + return ['ui', 'workspace', 'web']; +} + +export class WebExtensionProcessor extends BaseProcessor { + private readonly isWebKind: boolean = false; + + constructor(manifest: Manifest, options: IPackageOptions) { + super(manifest); + this.isWebKind = options.web && isWebKind(manifest); + } + + onFile(file: IFile): Promise { + if (this.isWebKind) { + const path = util.normalize(file.path); + if (/\.svg$/i.test(path)) { + throw new Error(`SVGs can't be used in a web extension: ${path}`); + } + this.assets.push({ type: `Microsoft.VisualStudio.Code.WebResources/${path}`, path }); + } + return Promise.resolve(file); + } + + async onEnd(): Promise { + if (this.assets.length > 25) { + throw new Error( + 'Cannot pack more than 25 files in a web extension. Use `vsce ls` to see all the files that will be packed and exclude those which are not needed in .vscodeignore.' + ); + } + if (this.isWebKind) { + this.vsix = { + ...this.vsix, + webExtension: true, + }; + this.tags = ['__web_extension']; + } + } +} + +export class NLSProcessor extends BaseProcessor { private translations: { [path: string]: string } = Object.create(null); constructor(manifest: Manifest) { super(manifest); - if (!manifest.contributes || !manifest.contributes.localizations || manifest.contributes.localizations.length === 0) { + if ( + !manifest.contributes || + !manifest.contributes.localizations || + manifest.contributes.localizations.length === 0 + ) { return; } @@ -646,7 +808,6 @@ export class NLSProcessor extends BaseProcessor { } export class ValidationProcessor extends BaseProcessor { - private files = new Map(); private duplicates = new Set(); @@ -669,7 +830,9 @@ export class ValidationProcessor extends BaseProcessor { return; } - const messages = [`The following files have the same case insensitive path, which isn't supported by the VSIX format:`]; + const messages = [ + `The following files have the same case insensitive path, which isn't supported by the VSIX format:`, + ]; for (const lower of this.duplicates) { for (const filePath of this.files.get(lower)) { @@ -717,14 +880,16 @@ export function validateManifest(manifest: Manifest): Manifest { throw new Error(`Badge URLs must come from an HTTPS source: ${badge.url}`); } - if (/\.svg$/i.test(srcUrl.pathname) && (!isHostTrusted(srcUrl))) { + if (/\.svg$/i.test(srcUrl.pathname) && !isHostTrusted(srcUrl)) { throw new Error(`Badge SVGs are restricted. Please use other file image formats, such as PNG: ${badge.url}`); } }); - Object.keys((manifest.dependencies || {})).forEach(dep => { + 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'?`); + throw new Error( + `You should not depend on 'vscode' in your 'dependencies'. Did you mean to add it to 'devDependencies'?` + ); } }); @@ -748,14 +913,14 @@ export function readNodeManifest(cwd = process.cwd()): Promise { export function readManifest(cwd = process.cwd(), nls = true): Promise { const manifest = readNodeManifest(cwd) .then(validateManifest); - + if (!nls) { return manifest; } - + const manifestNLSPath = path.join(cwd, 'package.nls.json'); const manifestNLS = readFile(manifestNLSPath, 'utf8') - .catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}')) + .catch(err => (err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}'))) .then(raw => { try { return Promise.resolve(JSON.parse(raw)); @@ -767,7 +932,6 @@ export function readManifest(cwd = process.cwd(), nls = true): Promise return Promise.all([manifest, manifestNLS]).then(([manifest, translations]) => { return patchNLS(manifest, translations); }); - } export function toVsixManifest(vsix: any): Promise { @@ -778,7 +942,7 @@ export function toVsixManifest(vsix: any): Promise { const defaultExtensions = { '.json': 'application/json', - '.vsixmanifest': 'text/xml' + '.vsixmanifest': 'text/xml', }; export function toContentTypes(files: IFile[]): Promise { @@ -789,7 +953,7 @@ export function toContentTypes(files: IFile[]): Promise { const allExtensions = { ...extensions, ...defaultExtensions }; const contentTypes = Object.keys(allExtensions).map(extension => ({ extension, - contentType: allExtensions[extension] + contentType: allExtensions[extension], })); return readFile(contentTypesTemplatePath, 'utf8') @@ -821,48 +985,73 @@ const defaultIgnore = [ '**/*.vsix', '**/.DS_Store', '**/*.vsixmanifest', - '**/.vscode-test/**' + '**/.vscode-test/**', ]; function collectAllFiles(cwd: string, manifest: Manifest, useYarn = false, dependencyEntryPoints?: string[]): Promise { return getDependencies(cwd, manifest, useYarn, dependencyEntryPoints).then(deps => { const promises: Promise[] = deps.map(dep => { - return glob('**', { cwd: dep.src, nodir: true, dot: true, ignore: 'node_modules/**' }) - .then(files => files - .map(f => { + return glob('**', { cwd: dep.src, nodir: true, dot: true, ignore: 'node_modules/**' }).then(files => + files.map(f => { return { src: path.relative(cwd, path.join(dep.src, f.replace(/\\/g, '/'))), dest: path.join(dep.dest, f).replace(/\\/g, '/') } - })) + }) + ) }); return Promise.all(promises).then(util.flatten); }); } -function collectFiles(cwd: string, manifest: Manifest, useYarn = false, dependencyEntryPoints?: string[], ignoreFile?: string): Promise { +function collectFiles( + cwd: string, + manifest: Manifest, + useYarn = false, + dependencyEntryPoints?: string[], + ignoreFile?: string +): Promise { return collectAllFiles(cwd, manifest, useYarn, dependencyEntryPoints).then(files => { files = files.filter(f => !/\r$/m.test(f.src)); - return readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8') - .catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : ignoreFile ? 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.src, i, MinimatchOptions)) || negate.some(i => minimatch(f.src, i.substr(1), MinimatchOptions)))); + return ( + readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8') + .catch(err => + err.code !== 'ENOENT' ? Promise.reject(err) : ignoreFile ? 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.src, i, MinimatchOptions)) || + negate.some(i => minimatch(f.src, i.substr(1), MinimatchOptions)) + ) + ) + ); }); } @@ -872,13 +1061,17 @@ export function processFiles(processors: IProcessor[], files: IFile[]): Promise< 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) => ({ ...r, ...p.vsix }), { assets }); + const tags = _(_.flatten(processors.map(p => p.tags))) + .uniq() // deduplicate + .compact() // remove falsey values + .join(','); + const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags }); return Promise.all([toVsixManifest(vsix), toContentTypes(files)]).then(result => { return [ { path: 'extension.vsixmanifest', contents: Buffer.from(result[0], 'utf8') }, { path: '[Content_Types].xml', contents: Buffer.from(result[1], 'utf8') }, - ...files + ...files, ]; }); }); @@ -894,7 +1087,8 @@ export function createDefaultProcessors(manifest: Manifest, options: IPackageOpt new LicenseProcessor(manifest), new IconProcessor(manifest), new NLSProcessor(manifest), - new ValidationProcessor(manifest) + new WebExtensionProcessor(manifest, options), + new ValidationProcessor(manifest), ]; } @@ -914,19 +1108,26 @@ export function collect(manifest: Manifest, options: IPackageOptions = {}): Prom function writeVsix(files: IFile[], packagePath: string): Promise { 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' ? Buffer.from(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()); - })); + .catch(err => (err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve(null))) + .then( + () => + new Promise((c, e) => { + const zip = new yazl.ZipFile(); + files.forEach(f => + isInMemoryFile(f) + ? zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(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()); + }) + ); } function getDefaultPackageName(manifest: Manifest): string { @@ -943,7 +1144,7 @@ async function prepublish(cwd: string, manifest: Manifest, useYarn: boolean = fa await new Promise((c, e) => { const tool = useYarn ? 'yarn' : 'npm'; const child = cp.spawn(tool, ['run', 'vscode:prepublish'], { cwd, shell: true, stdio: 'inherit' }); - child.on('exit', code => code === 0 ? c() : e(`${tool} failed with exit code ${code}`)); + child.on('exit', code => (code === 0 ? c() : e(`${tool} failed with exit code ${code}`))); child.on('error', e); }); } @@ -970,13 +1171,16 @@ export async function pack(options: IPackageOptions = {}): Promise /\.js$/i.test(f.path)); if (files.length > 5000 || jsFiles.length > 100) { - console.log(`This extension consists of ${files.length} files, out of which ${jsFiles.length} are JavaScript 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`); + console.log( + `This extension consists of ${files.length} files, out of which ${jsFiles.length} are JavaScript 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 = await getPackagePath(cwd, manifest, options); @@ -1006,16 +1210,25 @@ export async function packageCommand(options: IPackageOptions = {}): Promise { - return readManifest(cwd) - .then(manifest => collectFiles(cwd, manifest, useYarn, packagedDependencies, ignoreFile)) +export function listFiles( + cwd = process.cwd(), + useYarn = false, + packagedDependencies?: string[], + ignoreFile?: string +): Promise { + return readManifest(cwd).then(manifest => collectFiles(cwd, manifest, useYarn, packagedDependencies, ignoreFile)) .then(files => files.map(f => f.src)); } /** * Lists the files included in the extension's package. Runs prepublish. */ -export function ls(cwd = process.cwd(), useYarn = false, packagedDependencies?: string[], ignoreFile?: string): Promise { +export function ls( + cwd = process.cwd(), + useYarn = false, + packagedDependencies?: string[], + ignoreFile?: string +): Promise { return readManifest(cwd) .then(manifest => prepublish(cwd, manifest, useYarn).then(() => manifest)) .then(manifest => collectFiles(cwd, manifest, useYarn, packagedDependencies, ignoreFile)) diff --git a/src/publicgalleryapi.ts b/src/publicgalleryapi.ts index cb47dea4c..89a3ca8fa 100644 --- a/src/publicgalleryapi.ts +++ b/src/publicgalleryapi.ts @@ -1,5 +1,11 @@ import { HttpClient, HttpClientResponse } from 'typed-rest-client/HttpClient'; -import { PublishedExtension, ExtensionQueryFlags, FilterCriteria, ExtensionQueryFilterType, TypeInfo } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; +import { + PublishedExtension, + ExtensionQueryFlags, + FilterCriteria, + ExtensionQueryFilterType, + TypeInfo, +} from 'azure-devops-node-api/interfaces/GalleryInterfaces'; import { IHeaders } from 'azure-devops-node-api/interfaces/common/VsoBaseInterfaces'; import { ContractSerializer } from 'azure-devops-node-api/Serialization'; @@ -11,11 +17,19 @@ export interface ExtensionQuery { readonly assetTypes?: string[]; } -export class PublicGalleryAPI { +export interface IExtensionsReport { + malicious: string[]; + web: { + publishers: string[]; + extensions: string[]; + }; +} - private client = new HttpClient('vsce'); +export class PublicGalleryAPI { + private readonly extensionsReportUrl = 'https://az764295.vo.msecnd.net/extensions/marketplace.json'; + private readonly client = new HttpClient('vsce'); - constructor(private baseUrl: string, private apiVersion = '3.0-preview.1') { } + constructor(private baseUrl: string, private apiVersion = '3.0-preview.1') {} private post(url: string, data: string, additionalHeaders?: IHeaders): Promise { return this.client.post(`${this.baseUrl}/_apis/public${url}`, data, additionalHeaders); @@ -31,18 +45,33 @@ export class PublicGalleryAPI { const data = JSON.stringify({ filters: [{ pageNumber, pageSize, criteria }], assetTypes, - flags: flags.reduce((memo, flag) => memo | flag, 0) + flags: flags.reduce((memo, flag) => memo | flag, 0), }); - const res = await this.post('/gallery/extensionquery', data, { Accept: `application/json;api-version=${this.apiVersion}`, 'Content-Type': 'application/json', }); + const res = await this.post('/gallery/extensionquery', data, { + Accept: `application/json;api-version=${this.apiVersion}`, + 'Content-Type': 'application/json', + }); const raw = JSON.parse(await res.readBody()); return ContractSerializer.deserialize(raw.results[0].extensions, TypeInfo.PublishedExtension, false, false); } async getExtension(extensionId: string, flags: ExtensionQueryFlags[] = []): Promise { - const query = { criteria: [{ filterType: ExtensionQueryFilterType.Name, value: extensionId }], flags, }; + const query = { criteria: [{ filterType: ExtensionQueryFilterType.Name, value: extensionId }], flags }; const extensions = await this.extensionQuery(query); - return extensions.filter(({ publisher: { publisherName: publisher }, extensionName: name }) => extensionId.toLowerCase() === `${publisher}.${name}`.toLowerCase())[0]; + return extensions.filter( + ({ publisher: { publisherName: publisher }, extensionName: name }) => + extensionId.toLowerCase() === `${publisher}.${name}`.toLowerCase() + )[0]; + } + + async getExtensionsReport(): Promise { + const res = await this.client.get(this.extensionsReportUrl); + const raw = >JSON.parse(await res.readBody()); + return { + malicious: raw.malicious || [], + web: raw.web || { publishers: [], extensions: [] }, + }; } } diff --git a/src/publish.ts b/src/publish.ts index 3375e8773..f8bfeee7b 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -1,16 +1,19 @@ import * as fs from 'fs'; import { ExtensionQueryFlags, PublishedExtension } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; -import { pack, readManifest, IPackage } from './package'; +import { pack, readManifest, IPackage, isWebKind, isSupportedWebExtension } from './package'; import * as tmp from 'tmp'; import { getPublisher } from './store'; -import { getGalleryAPI, read, getPublishedUrl, log } from './util'; +import { getGalleryAPI, read, getPublishedUrl, log, getPublicGalleryAPI } from './util'; import { Manifest } from './manifest'; import * as denodeify from 'denodeify'; import * as yauzl from 'yauzl'; import * as semver from 'semver'; import * as cp from 'child_process'; -const exec = denodeify(cp.exec as any, (err, stdout, stderr) => [err, { stdout, stderr }]); +const exec = denodeify( + cp.exec as any, + (err, stdout, stderr) => [err, { stdout, stderr }] +); const tmpName = denodeify(tmp.tmpName); function readManifestFromPackage(packagePath: string): Promise { @@ -60,8 +63,9 @@ async function _publish(packagePath: string, pat: string, manifest: Manifest): P const fullName = `${name}@${manifest.version}`; console.log(`Publishing ${fullName}...`); - return api.getExtension(null, manifest.publisher, manifest.name, null, ExtensionQueryFlags.IncludeVersions) - .catch(err => err.statusCode === 404 ? null : Promise.reject(err)) + return api + .getExtension(null, manifest.publisher, manifest.name, null, 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.`); @@ -73,10 +77,16 @@ async function _publish(packagePath: string, pat: string, manifest: Manifest): P return promise .catch(err => Promise.reject(err.statusCode === 409 ? `${fullName} already exists.` : err)) - .then(() => log.done(`Published ${fullName}\nYour extension will live at ${getPublishedUrl(name)} (might take a few minutes for it to show up).`)); + .then(() => + log.done( + `Published ${fullName}\nYour extension will live at ${getPublishedUrl( + name + )} (might take a few minutes for it to show up).` + ) + ); }) .catch(err => { - const message = err && err.message || ''; + 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`; @@ -92,11 +102,13 @@ export interface IPublishOptions { commitMessage?: string; cwd?: string; pat?: string; + githubBranch?: string; baseContentUrl?: string; baseImagesUrl?: string; useYarn?: boolean; noVerify?: boolean; ignoreFile?: string; + web?: boolean; } async function versionBump(cwd: string = process.cwd(), version?: string, commitMessage?: string): Promise { @@ -151,29 +163,46 @@ export function publish(options: IPublishOptions = {}): Promise { if (options.version) { return Promise.reject(`Not supported: packagePath and version.`); } + if (options.web) { + return Promise.reject(`Not supported: packagePath and web.`); + } - promise = readManifestFromPackage(options.packagePath) - .then(manifest => ({ manifest, packagePath: options.packagePath })); + promise = readManifestFromPackage(options.packagePath).then(manifest => ({ + manifest, + packagePath: options.packagePath, + })); } else { const cwd = options.cwd; + const githubBranch = options.githubBranch; const baseContentUrl = options.baseContentUrl; const baseImagesUrl = options.baseImagesUrl; const useYarn = options.useYarn; const ignoreFile = options.ignoreFile; + const web = options.web; promise = versionBump(options.cwd, options.version, options.commitMessage) .then(() => tmpName()) - .then(packagePath => pack({ packagePath, cwd, baseContentUrl, baseImagesUrl, useYarn, ignoreFile })); + .then(packagePath => + pack({ packagePath, cwd, githubBranch, baseContentUrl, baseImagesUrl, useYarn, ignoreFile, web }) + ); } - return promise.then(({ manifest, packagePath }) => { + return promise.then(async ({ manifest, packagePath }) => { if (!options.noVerify && manifest.enableProposedApi) { - throw new Error('Extensions using proposed API (enableProposedApi: true) can\'t be published to the Marketplace'); + throw new Error("Extensions using proposed API (enableProposedApi: true) can't be published to the Marketplace"); + } + + if (options.web) { + if (!isWebKind(manifest)) { + throw new Error("Extensions which are not web kind can't be published to the Marketpalce as a web extension"); + } + const extensionsReport = await getPublicGalleryAPI().getExtensionsReport(); + if (!isSupportedWebExtension(manifest, extensionsReport)) { + throw new Error("Extensions which are not supported can't be published to the Marketpalce as a web extension"); + } } - const patPromise = options.pat - ? Promise.resolve(options.pat) - : getPublisher(manifest.publisher).then(p => p.pat); + const patPromise = options.pat ? Promise.resolve(options.pat) : getPublisher(manifest.publisher).then(p => p.pat); return patPromise.then(pat => _publish(packagePath, pat, manifest)); }); diff --git a/src/search.ts b/src/search.ts index d91af3324..8062bef83 100644 --- a/src/search.ts +++ b/src/search.ts @@ -9,9 +9,7 @@ export async function search(searchText: string, json: boolean = false): Promise const api = getPublicGalleryAPI(); const results = await api.extensionQuery({ pageSize, - criteria: [ - { filterType: ExtensionQueryFilterType.SearchText, value: searchText }, - ], + criteria: [{ filterType: ExtensionQueryFilterType.SearchText, value: searchText }], flags, }); @@ -25,18 +23,21 @@ export async function search(searchText: string, json: boolean = false): Promise return; } - console.log([ - `Search results:`, - '', - ...tableView([ - ['', ''], - ...results.map(({ publisher: { publisherName }, extensionName, shortDescription }) => - [publisherName + '.' + extensionName, (shortDescription || '').replace(/\n|\r|\t/g, ' ')] - ) - ]), - '', - 'For more information on an extension use "vsce show "', - ] - .map(line => wordTrim(line.replace(/\s+$/g, ''))) - .join('\n')); + console.log( + [ + `Search results:`, + '', + ...tableView([ + ['', ''], + ...results.map(({ publisher: { publisherName }, extensionName, shortDescription }) => [ + publisherName + '.' + extensionName, + (shortDescription || '').replace(/\n|\r|\t/g, ' '), + ]), + ]), + '', + 'For more information on an extension use "vsce show "', + ] + .map(line => wordTrim(line.replace(/\s+$/g, ''))) + .join('\n') + ); } diff --git a/src/show.ts b/src/show.ts index 36b53577d..fd681fbc2 100644 --- a/src/show.ts +++ b/src/show.ts @@ -38,63 +38,59 @@ function showOverview({ extensionName = 'unknown', shortDescription = '', versions = [], - publisher: { - displayName: publisherDisplayName, - publisherName - }, + publisher: { displayName: publisherDisplayName, publisherName }, categories = [], tags = [], statistics = [], publishedDate, lastUpdated, }: PublishedExtension) { - const [{ version = 'unknown' } = {}] = versions; // Create formatted table list of versions - const versionList = versions - .slice(0, limitVersions) - .map(({ version, lastUpdated }) => [version, formatDate(lastUpdated)]); + const versionList = ( + versions.slice(0, limitVersions).map(({ version, lastUpdated }) => [version, formatDate(lastUpdated)]) + ); - const { - install: installs = 0, - averagerating = 0, - ratingcount = 0, - } = statistics - .reduce((map, { statisticName, value }) => ({ ...map, [statisticName]: value }), {}); + const { install: installs = 0, averagerating = 0, ratingcount = 0 } = statistics.reduce( + (map, { statisticName, value }) => ({ ...map, [statisticName]: value }), + {} + ); // Render - console.log([ - `${displayName}`, - `${publisherDisplayName} | ${icons.download} ` + - `${Number(installs).toLocaleString()} installs |` + - ` ${ratingStars(averagerating)} (${ratingcount})`, - '', - `${shortDescription}`, - '', - 'Recent versions:', - ...(versionList.length ? tableView(versionList).map(indentRow) : ['no versions found']), - '', - 'Categories:', - ` ${categories.join(', ')}`, - '', - 'Tags:', - ` ${tags.filter(tag => !isExtensionTag.test(tag)).join(', ')}`, - '', - 'More info:', - ...tableView([ - ['Unique identifier:', `${publisherName}.${extensionName}`], - ['Version:', version], - ['Last updated:', formatDateTime(lastUpdated)], - ['Publisher:', publisherDisplayName], - ['Published at:', formatDate(publishedDate)], - ]) - .map(indentRow), - '', - 'Statistics:', - ...tableView(statistics.map(({ statisticName, value }) => [statisticName, Number(value).toFixed(2)])) - .map(indentRow), - ] - .map(line => wordWrap(line)) - .join('\n')); + console.log( + [ + `${displayName}`, + `${publisherDisplayName} | ${icons.download} ` + + `${Number(installs).toLocaleString()} installs |` + + ` ${ratingStars(averagerating)} (${ratingcount})`, + '', + `${shortDescription}`, + '', + 'Recent versions:', + ...(versionList.length ? tableView(versionList).map(indentRow) : ['no versions found']), + '', + 'Categories:', + ` ${categories.join(', ')}`, + '', + 'Tags:', + ` ${tags.filter(tag => !isExtensionTag.test(tag)).join(', ')}`, + '', + 'More info:', + ...tableView([ + ['Unique identifier:', `${publisherName}.${extensionName}`], + ['Version:', version], + ['Last updated:', formatDateTime(lastUpdated)], + ['Publisher:', publisherDisplayName], + ['Published at:', formatDate(publishedDate)], + ]).map(indentRow), + '', + 'Statistics:', + ...tableView( + statistics.map(({ statisticName, value }) => [statisticName, Number(value).toFixed(2)]) + ).map(indentRow), + ] + .map(line => wordWrap(line)) + .join('\n') + ); } diff --git a/src/store.ts b/src/store.ts index 911f59093..be0daad50 100644 --- a/src/store.ts +++ b/src/store.ts @@ -25,7 +25,7 @@ export interface IGetOptions { function load(): Promise { return readFile(storePath, 'utf8') - .catch(err => err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}')) + .catch(err => (err.code !== 'ENOENT' ? Promise.reject(err) : Promise.resolve('{}'))) .then(rawStore => { try { return Promise.resolve(JSON.parse(rawStore)); @@ -40,8 +40,7 @@ function load(): Promise { } function save(store: IStore): Promise { - return writeFile(storePath, JSON.stringify(store), {mode: '0600'}) - .then(() => store); + return writeFile(storePath, JSON.stringify(store), { mode: '0600' }).then(() => store); } function addPublisherToStore(store: IStore, publisher: IPublisher): Promise { @@ -84,8 +83,9 @@ export function loginPublisher(publisherName: string): Promise { if (publisher) { console.log(`Publisher '${publisherName}' is already known`); - return read('Do you want to overwrite its PAT? [y/N] ') - .then(answer => /^y$/i.test(answer) ? store : Promise.reject('Aborted')); + return read('Do you want to overwrite its PAT? [y/N] ').then(answer => + /^y$/i.test(answer) ? store : Promise.reject('Aborted') + ); } return Promise.resolve(store); @@ -110,36 +110,41 @@ export function logoutPublisher(publisherName: string): Promise { export function createPublisher(publisherName: string): Promise { validatePublisher(publisherName); - return read(`Publisher human-friendly name: `, { default: publisherName }).then(displayName => { - return read(`E-mail: `).then(email => { - return read(`Personal Access Token:`, { silent: true, replace: '*' }) - .then(async pat => { - const api = await getGalleryAPI(pat); - const raw = { - publisherName, - displayName, - extensions: [], - flags: null, - lastUpdated: null, - longDescription: '', - publisherId: null, - shortDescription: '', - emailAddress: [email] - }; - - await api.createPublisher(raw); - return { name: publisherName, pat }; - }) - .then(publisher => load().then(store => addPublisherToStore(store, publisher))); - }); - }) + log.warn( + 'Creating a publisher via vsce is deprecated and will be removed soon. You can create a publisher directly in the Marketplace: https://aka.ms/vscode-create-publisher' + ); + + return read(`Publisher human-friendly name: `, { default: publisherName }) + .then(displayName => { + return read(`E-mail: `).then(email => { + return read(`Personal Access Token:`, { silent: true, replace: '*' }) + .then(async pat => { + const api = await getGalleryAPI(pat); + const raw = { + publisherName, + displayName, + extensions: [], + flags: null, + lastUpdated: null, + longDescription: '', + publisherId: null, + shortDescription: '', + emailAddress: [email], + }; + + await api.createPublisher(raw); + return { name: publisherName, pat }; + }) + .then(publisher => load().then(store => addPublisherToStore(store, publisher))); + }); + }) .then(() => log.done(`Created publisher '${publisherName}'.`)); } export function deletePublisher(publisherName: string): Promise { return getPublisher(publisherName).then(({ pat }) => { return read(`This will FOREVER delete '${publisherName}'! Are you sure? [y/N] `) - .then(answer => /^y$/i.test(answer) ? null : Promise.reject('Aborted')) + .then(answer => (/^y$/i.test(answer) ? null : Promise.reject('Aborted'))) .then(() => getGalleryAPI(pat)) .then(api => api.deletePublisher(publisherName)) .then(() => load().then(store => removePublisherFromStore(store, publisherName))) diff --git a/src/test/fixtures/readme/readme.branch.main.expected.md b/src/test/fixtures/readme/readme.branch.main.expected.md new file mode 100644 index 000000000..d6ef36667 --- /dev/null +++ b/src/test/fixtures/readme/readme.branch.main.expected.md @@ -0,0 +1,48 @@ +# README + +>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. + +This README covers off: +* [Functionality](#functionality) +* [Install](#install) +* [Run and Configure](#run-and-configure) +* [Known Issues/Bugs](#known-issuesbugs) +* [Backlog](#backlog) +* [How to Debug](#how-to-debug) + +# Functionality + +Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. + +![Underscores and hovers](https://github.com/username/repository/raw/main/images/SpellMDDemo1.gif) + +The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. + +[![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) +[![Jump to issues](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey) +![](https://github.com/username/repository/raw/main/images/SpellMDDemo2.gif) + + +The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. + +![Add to dictionary](https://github.com/username/repository/raw/main/images/SpellMDDemo3.gif) + +![issue](https://github.com/username/repository/raw/main/issue) + +[mono](https://github.com/username/repository/blob/main/monkey) +[not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) + +# Install +This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. + + +To clone the extension and load locally... + +``` +git clone https://github.com/Microsoft/vscode-SpellMD.git +npm install +tsc +``` + +>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. diff --git a/src/test/fixtures/readme/readme.branch.override.content.expected.md b/src/test/fixtures/readme/readme.branch.override.content.expected.md new file mode 100644 index 000000000..8bf6e2c90 --- /dev/null +++ b/src/test/fixtures/readme/readme.branch.override.content.expected.md @@ -0,0 +1,48 @@ +# README + +>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. + +This README covers off: +* [Functionality](#functionality) +* [Install](#install) +* [Run and Configure](#run-and-configure) +* [Known Issues/Bugs](#known-issuesbugs) +* [Backlog](#backlog) +* [How to Debug](#how-to-debug) + +# Functionality + +Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. + +![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif) + +The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. + +[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) +[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/base/monkey) +![](https://github.com/base/images/SpellMDDemo2.gif) + + +The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. + +![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif) + +![issue](https://github.com/base/issue) + +[mono](https://github.com/base/monkey) +[not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) + +# Install +This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. + + +To clone the extension and load locally... + +``` +git clone https://github.com/Microsoft/vscode-SpellMD.git +npm install +tsc +``` + +>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. diff --git a/src/test/fixtures/readme/readme.branch.override.images.expected.md b/src/test/fixtures/readme/readme.branch.override.images.expected.md new file mode 100644 index 000000000..9df6b7e93 --- /dev/null +++ b/src/test/fixtures/readme/readme.branch.override.images.expected.md @@ -0,0 +1,48 @@ +# README + +>**Important:** Once installed the checker will only update if you add the setting `"spellMD.enable": true` to your `.vscode\settings.json` file. + +This README covers off: +* [Functionality](#functionality) +* [Install](#install) +* [Run and Configure](#run-and-configure) +* [Known Issues/Bugs](#known-issuesbugs) +* [Backlog](#backlog) +* [How to Debug](#how-to-debug) + +# Functionality + +Load up a Markdown file and get highlights and hovers for existing issues. Checking will occur as you type in the document. + +![Underscores and hovers](https://github.com/base/images/SpellMDDemo1.gif) + +The status bar lets you quickly navigate to any issue and you can see all positions in the gutter. + +[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](http://shouldnottouchthis/) +[![Jump to issues](https://github.com/base/images/SpellMDDemo2.gif)](https://github.com/username/repository/blob/main/monkey) +![](https://github.com/base/images/SpellMDDemo2.gif) + + +The `spellMD.json` config file is watched so you can add more ignores or change mappings at will. + +![Add to dictionary](https://github.com/base/images/SpellMDDemo3.gif) + +![issue](https://github.com/base/issue) + +[mono](https://github.com/username/repository/blob/main/monkey) +[not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) + +# Install +This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. + + +To clone the extension and load locally... + +``` +git clone https://github.com/Microsoft/vscode-SpellMD.git +npm install +tsc +``` + +>**Note:** TypeScript 1.6 or higher is required you can check with `tsc -v` and if you need to upgrade then run `npm install -g typescript`. diff --git a/src/test/fixtures/readme/readme.expected.md b/src/test/fixtures/readme/readme.expected.md index 67628fe33..46ab381d3 100644 --- a/src/test/fixtures/readme/readme.expected.md +++ b/src/test/fixtures/readme/readme.expected.md @@ -31,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change [mono](https://github.com/username/repository/blob/master/monkey) [not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) # Install This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. diff --git a/src/test/fixtures/readme/readme.github.expected.md b/src/test/fixtures/readme/readme.github.expected.md index f28c96ceb..5c1f8050a 100644 --- a/src/test/fixtures/readme/readme.github.expected.md +++ b/src/test/fixtures/readme/readme.github.expected.md @@ -12,3 +12,4 @@ * foo/$234/#7 * [#7](http://shouldnottouchthis/) * [other/repositoryName#8](http://shouldnottouchthis/) + * [Email me](MAILTO:example@example.com) diff --git a/src/test/fixtures/readme/readme.github.md b/src/test/fixtures/readme/readme.github.md index 51355f137..7d40e6792 100644 --- a/src/test/fixtures/readme/readme.github.md +++ b/src/test/fixtures/readme/readme.github.md @@ -12,3 +12,4 @@ * foo/$234/#7 * [#7](http://shouldnottouchthis/) * [other/repositoryName#8](http://shouldnottouchthis/) + * [Email me](MAILTO:example@example.com) diff --git a/src/test/fixtures/readme/readme.images.expected.md b/src/test/fixtures/readme/readme.images.expected.md index cae365420..19dc6d7fc 100644 --- a/src/test/fixtures/readme/readme.images.expected.md +++ b/src/test/fixtures/readme/readme.images.expected.md @@ -31,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change [mono](https://github.com/username/repository/blob/master/monkey) [not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) # Install This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. diff --git a/src/test/fixtures/readme/readme.md b/src/test/fixtures/readme/readme.md index 970b9b6f6..c28a5ee0b 100644 --- a/src/test/fixtures/readme/readme.md +++ b/src/test/fixtures/readme/readme.md @@ -31,6 +31,7 @@ The `spellMD.json` config file is watched so you can add more ignores or change [mono](monkey) [not](http://shouldnottouchthis/) +[Email me](mailto:example@example.com) # Install This extension is published in the VS Code Gallery. So simply hit 'F1' and type 'ext inst' from there select `SpellMD` and follow instructions. diff --git a/src/test/package.test.ts b/src/test/package.test.ts index 3f35bdc84..30057c8fb 100644 --- a/src/test/package.test.ts +++ b/src/test/package.test.ts @@ -1,7 +1,19 @@ import { - readManifest, collect, toContentTypes, ReadmeProcessor, - read, processFiles, createDefaultProcessors, - toVsixManifest, IFile, validateManifest + readManifest, + collect, + toContentTypes, + ReadmeProcessor, + read, + processFiles, + createDefaultProcessors, + toVsixManifest, + IFile, + validateManifest, + isSupportedWebExtension, + WebExtensionProcessor, + IAsset, + IPackageOptions, + ILocalFile, } from '../package'; import { Manifest } from '../manifest'; import * as path from 'path'; @@ -10,6 +22,7 @@ import * as assert from 'assert'; import { parseString } from 'xml2js'; import * as denodeify from 'denodeify'; import * as _ from 'lodash'; +import { IExtensionsReport } from '../publicgalleryapi'; // don't warn in tests console.warn = () => null; @@ -33,43 +46,46 @@ async function throws(fn: () => Promise): Promise { const fixture = name => path.join(path.dirname(path.dirname(__dirname)), 'src', 'test', 'fixtures', name); const readFile = denodeify(fs.readFile); -function createXMLParser(): (raw: string) => Promise { return denodeify(parseString); } +function createXMLParser(): (raw: string) => Promise { + return denodeify(parseString); +} type XMLManifest = { PackageManifest: { - $: { Version: string, xmlns: string, }, + $: { Version: string; xmlns: string }; Metadata: { - Description: { _: string; }[], - DisplayName: string[], - Identity: { $: { Id: string, Version: string, Publisher: string } }[], - Tags: string[], - GalleryFlags: string[], - License: string[], - Icon: string[], - Properties: { Property: { $: { Id: string, Value: string } }[] }[], - Categories: string[], - Badges: { Badge: { $: { Link: string, ImgUri: string, Description: string } }[] }[] - }[], - Installation: { InstallationTarget: { $: { Id: string } }[] }[] - Dependencies: string[] - Assets: { Asset: { $: { Type: string, Path: string } }[] }[] - } + Description: { _: string }[]; + DisplayName: string[]; + Identity: { $: { Id: string; Version: string; Publisher: string } }[]; + Tags: string[]; + GalleryFlags: string[]; + License: string[]; + Icon: string[]; + Properties: { Property: { $: { Id: string; Value: string } }[] }[]; + Categories: string[]; + Badges: { Badge: { $: { Link: string; ImgUri: string; Description: string } }[] }[]; + }[]; + Installation: { InstallationTarget: { $: { Id: string } }[] }[]; + Dependencies: string[]; + Assets: { Asset: { $: { Type: string; Path: string } }[] }[]; + }; }; type ContentTypes = { Types: { - Default: { $: { Extension: string, ContentType } }[] - } + Default: { $: { Extension: string; ContentType } }[]; + }; }; const parseXmlManifest = createXMLParser(); const parseContentTypes = createXMLParser(); -function _toVsixManifest(manifest: Manifest, files: IFile[]): Promise { - const processors = createDefaultProcessors(manifest); +function _toVsixManifest(manifest: Manifest, files: IFile[], options: IPackageOptions = {}): Promise { + const processors = createDefaultProcessors(manifest, options); return processFiles(processors, files).then(() => { const assets = _.flatten(processors.map(p => p.assets)); - const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets }); + const tags = _(_.flatten(processors.map(p => p.tags))).join(','); + const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags }); return toVsixManifest(vsix); }); @@ -100,7 +116,7 @@ function createManifest(extra: Partial): Manifest { version: '0.0.1', description: 'test extension', engines: { vscode: '*' }, - ...extra + ...extra, }; } @@ -229,7 +245,7 @@ describe('collect', function () { assert.equal(manifest.name, 'package-b'); - const files = await collect(manifest, { cwd, useYarn: true }); + const files = await collect(manifest, { cwd, useYarn: true }) as ILocalFile[]; [ { @@ -267,18 +283,16 @@ describe('collect', function () { }); describe('readManifest', () => { - it('should patch NLS', () => { const cwd = fixture('nls'); const raw = require(path.join(cwd, 'package.json')); const translations = require(path.join(cwd, 'package.nls.json')); - return readManifest(cwd) - .then((manifest: any) => { - assert.equal(manifest.name, raw.name); - assert.equal(manifest.description, translations['extension.description']); - assert.equal(manifest.contributes.debuggers[0].label, translations['node.label']); - }); + return readManifest(cwd).then((manifest: any) => { + assert.equal(manifest.name, raw.name); + assert.equal(manifest.description, translations['extension.description']); + assert.equal(manifest.contributes.debuggers[0].label, translations['node.label']); + }); }); it('should not patch NLS if required', () => { @@ -286,48 +300,109 @@ describe('readManifest', () => { const raw = require(path.join(cwd, 'package.json')); const translations = require(path.join(cwd, 'package.nls.json')); - return readManifest(cwd, false) - .then((manifest: any) => { - assert.equal(manifest.name, raw.name); - assert.notEqual(manifest.description, translations['extension.description']); - assert.notEqual(manifest.contributes.debuggers[0].label, translations['node.label']); - }); + return readManifest(cwd, false).then((manifest: any) => { + assert.equal(manifest.name, raw.name); + assert.notEqual(manifest.description, translations['extension.description']); + assert.notEqual(manifest.contributes.debuggers[0].label, translations['node.label']); + }); }); }); describe('validateManifest', () => { it('should catch missing fields', () => { assert(validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: { vscode: '0.10.1' } })); - assert.throws(() => { validateManifest({ publisher: null, name: 'demo', version: '1.0.0', engines: { vscode: '0.10.1' } }); }); - assert.throws(() => { validateManifest({ publisher: 'demo', name: null, version: '1.0.0', engines: { vscode: '0.10.1' } }); }); - assert.throws(() => { validateManifest({ publisher: 'demo', name: 'demo', version: null, engines: { vscode: '0.10.1' } }); }); - assert.throws(() => { validateManifest({ publisher: 'demo', name: 'demo', version: '1.0', engines: { vscode: '0.10.1' } }); }); - assert.throws(() => { validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: null }); }); - assert.throws(() => { validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: { vscode: null } }); }); + assert.throws(() => { + validateManifest({ publisher: null, name: 'demo', version: '1.0.0', engines: { vscode: '0.10.1' } }); + }); + assert.throws(() => { + validateManifest({ publisher: 'demo', name: null, version: '1.0.0', engines: { vscode: '0.10.1' } }); + }); + assert.throws(() => { + validateManifest({ publisher: 'demo', name: 'demo', version: null, engines: { vscode: '0.10.1' } }); + }); + assert.throws(() => { + validateManifest({ publisher: 'demo', name: 'demo', version: '1.0', engines: { vscode: '0.10.1' } }); + }); + assert.throws(() => { + validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: null }); + }); + assert.throws(() => { + validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: { vscode: null } }); + }); }); it('should prevent SVG icons', () => { assert(validateManifest(createManifest({ icon: 'icon.png' }))); - assert.throws(() => { validateManifest(createManifest({ icon: 'icon.svg' })); }); + assert.throws(() => { + validateManifest(createManifest({ icon: 'icon.svg' })); + }); }); it('should prevent badges from non HTTPS sources', () => { - assert.throws(() => { validateManifest(createManifest({ badges: [{ url: 'relative.png', href: 'http://badgeurl', description: 'this is a badge' }] })); }); - assert.throws(() => { validateManifest(createManifest({ badges: [{ url: 'relative.svg', href: 'http://badgeurl', description: 'this is a badge' }] })); }); - assert.throws(() => { validateManifest(createManifest({ badges: [{ url: 'http://badgeurl.png', href: 'http://badgeurl', description: 'this is a badge' }] })); }); + assert.throws(() => { + validateManifest( + createManifest({ badges: [{ url: 'relative.png', href: 'http://badgeurl', description: 'this is a badge' }] }) + ); + }); + assert.throws(() => { + validateManifest( + createManifest({ badges: [{ url: 'relative.svg', href: 'http://badgeurl', description: 'this is a badge' }] }) + ); + }); + assert.throws(() => { + validateManifest( + createManifest({ + badges: [{ url: 'http://badgeurl.png', href: 'http://badgeurl', description: 'this is a badge' }], + }) + ); + }); }); it('should allow non SVG badges', () => { - assert(validateManifest(createManifest({ badges: [{ url: 'https://host/badge.png', href: 'http://badgeurl', description: 'this is a badge' }] }))); + assert( + validateManifest( + createManifest({ + badges: [{ url: 'https://host/badge.png', href: 'http://badgeurl', description: 'this is a badge' }], + }) + ) + ); }); it('should allow SVG badges from trusted sources', () => { - assert(validateManifest(createManifest({ badges: [{ url: 'https://gemnasium.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }] }))); + assert( + validateManifest( + createManifest({ + badges: [{ url: 'https://gemnasium.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], + }) + ) + ); }); it('should prevent SVG badges from non trusted sources', () => { - assert.throws(() => { assert(validateManifest(createManifest({ badges: [{ url: 'https://github.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }] }))); }); - assert.throws(() => { assert(validateManifest(createManifest({ badges: [{ url: 'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/410.sv%67', href: 'http://badgeurl', description: 'this is a badge' }] }))); }); + assert.throws(() => { + assert( + validateManifest( + createManifest({ + badges: [{ url: 'https://github.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], + }) + ) + ); + }); + assert.throws(() => { + assert( + validateManifest( + createManifest({ + badges: [ + { + url: 'https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/410.sv%67', + href: 'http://badgeurl', + description: 'this is a badge', + }, + ], + }) + ) + ); + }); }); }); @@ -338,7 +413,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; return _toVsixManifest(manifest, []) @@ -349,7 +424,10 @@ describe('toVsixManifest', () => { assert.ok(result.PackageManifest.$); assert.equal(result.PackageManifest.$.Version, '2.0.0'); assert.equal(result.PackageManifest.$.xmlns, 'http://schemas.microsoft.com/developer/vsx-schema/2011'); - assert.equal(result.PackageManifest.$['xmlns:d'], 'http://schemas.microsoft.com/developer/vsx-schema-design/2011'); + assert.equal( + result.PackageManifest.$['xmlns:d'], + 'http://schemas.microsoft.com/developer/vsx-schema-design/2011' + ); assert.ok(result.PackageManifest.Metadata); assert.equal(result.PackageManifest.Metadata.length, 1); assert.equal(result.PackageManifest.Metadata[0].Description[0]._, 'test extension'); @@ -370,7 +448,7 @@ describe('toVsixManifest', () => { }); }); - it("should escape special characters", () => { + it('should escape special characters', () => { const specialCharacters = '\'"<>&`'; const name = `name${specialCharacters}`; @@ -379,8 +457,11 @@ describe('toVsixManifest', () => { const description = `description${specialCharacters}`; const manifest = { - name, publisher, version, description, - engines: Object.create(null) + name, + publisher, + version, + description, + engines: Object.create(null), }; return _toVsixManifest(manifest, []) @@ -399,18 +480,19 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; - const files = [ - { path: 'extension/readme.md', contents: Buffer.from('') } - ]; + const files = [{ path: 'extension/readme.md', contents: Buffer.from('') }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.Details'); + assert.equal( + result.PackageManifest.Assets[0].Asset[1].$.Type, + 'Microsoft.VisualStudio.Services.Content.Details' + ); assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/readme.md'); }); }); @@ -421,18 +503,19 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; - const files = [ - { path: 'extension/changelog.md', contents: Buffer.from('') } - ]; + const files = [{ path: 'extension/changelog.md', contents: Buffer.from('') }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.Changelog'); + assert.equal( + result.PackageManifest.Assets[0].Asset[1].$.Type, + 'Microsoft.VisualStudio.Services.Content.Changelog' + ); assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/changelog.md'); }); }); @@ -443,7 +526,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', displayName: 'Test Extension', - engines: Object.create(null) + engines: Object.create(null), }; return _toVsixManifest(manifest, []) @@ -461,18 +544,19 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', license: 'SEE LICENSE IN thelicense.md', - engines: Object.create(null) + engines: Object.create(null), }; - const files = [ - { path: 'extension/thelicense.md' } - ]; + const files = [{ path: 'extension/thelicense.md', contents: '' }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.License'); + assert.equal( + result.PackageManifest.Assets[0].Asset[1].$.Type, + 'Microsoft.VisualStudio.Services.Content.License' + ); assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/thelicense.md'); }); }); @@ -484,12 +568,10 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', license: 'SEE LICENSE IN thelicense.md', - engines: Object.create(null) + engines: Object.create(null), }; - const files = [ - { path: 'extension/thelicense.md' } - ]; + const files = [{ path: 'extension/thelicense.md', contents: '' }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) @@ -506,12 +588,10 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; - const files = [ - { path: 'extension/LICENSE.md' } - ]; + const files = [{ path: 'extension/LICENSE.md', contents: '' }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) @@ -520,7 +600,10 @@ describe('toVsixManifest', () => { assert.equal(result.PackageManifest.Metadata[0].License.length, 1); assert.equal(result.PackageManifest.Metadata[0].License[0], 'extension/LICENSE.md'); assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.License'); + assert.equal( + result.PackageManifest.Assets[0].Asset[1].$.Type, + 'Microsoft.VisualStudio.Services.Content.License' + ); assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/LICENSE.md'); }); }); @@ -533,12 +616,12 @@ describe('toVsixManifest', () => { description: 'test extension', engines: Object.create(null), icon: 'fake.png', - license: 'SEE LICENSE IN thelicense.md' + license: 'SEE LICENSE IN thelicense.md', }; const files = [ - { path: 'extension/fake.png' }, - { path: 'extension/thelicense.md' } + { path: 'extension/fake.png', contents: '' }, + { path: 'extension/thelicense.md', contents: '' }, ]; return _toVsixManifest(manifest, files) @@ -558,17 +641,19 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - icon: 'fake.png' + icon: 'fake.png', }; - const files = [ - { path: 'extension/fake.png' } - ]; + const files = [{ path: 'extension/fake.png', contents: '' }]; return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { - assert.ok(result.PackageManifest.Assets[0].Asset.some(d => d.$.Type === 'Microsoft.VisualStudio.Services.Icons.Default' && d.$.Path === 'extension/fake.png')); + assert.ok( + result.PackageManifest.Assets[0].Asset.some( + d => d.$.Type === 'Microsoft.VisualStudio.Services.Icons.Default' && d.$.Path === 'extension/fake.png' + ) + ); }); }); @@ -580,12 +665,12 @@ describe('toVsixManifest', () => { description: 'test extension', engines: Object.create(null), icon: 'fake.png', - license: 'SEE LICENSE IN thelicense.md' + license: 'SEE LICENSE IN thelicense.md', }; const files = [ - { path: 'extension\\fake.png' }, - { path: 'extension\\thelicense.md' } + { path: 'extension\\fake.png', contents: '' }, + { path: 'extension\\thelicense.md', contents: '' }, ]; return _toVsixManifest(manifest, files) @@ -606,16 +691,20 @@ describe('toVsixManifest', () => { engines: Object.create(null), galleryBanner: { color: '#5c2d91', - theme: 'dark' - } + theme: 'dark', + }, }; return _toVsixManifest(manifest, []) .then(xml => parseXmlManifest(xml)) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property.map(p => p.$); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Branding.Color' && p.Value === '#5c2d91')); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Branding.Theme' && p.Value === 'dark')); + assert.ok( + properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Branding.Color' && p.Value === '#5c2d91') + ); + assert.ok( + properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Branding.Theme' && p.Value === 'dark') + ); }); }); @@ -626,24 +715,54 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), repository: { - type: "git", - url: "https://server.com/Microsoft/vscode-spell-check.git" + type: 'git', + url: 'https://server.com/Microsoft/vscode-spell-check.git', }, bugs: { - url: "https://server.com/Microsoft/vscode-spell-check/issues" + url: 'https://server.com/Microsoft/vscode-spell-check/issues', }, - homepage: "https://server.com/Microsoft/vscode-spell-check", + homepage: 'https://server.com/Microsoft/vscode-spell-check', }; return _toVsixManifest(manifest, []) .then(xml => parseXmlManifest(xml)) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property.map(p => p.$); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.Source' && p.Value === 'https://server.com/Microsoft/vscode-spell-check.git')); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.Getstarted' && p.Value === 'https://server.com/Microsoft/vscode-spell-check.git')); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.Repository' && p.Value === 'https://server.com/Microsoft/vscode-spell-check.git')); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.Support' && p.Value === 'https://server.com/Microsoft/vscode-spell-check/issues')); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.Learn' && p.Value === 'https://server.com/Microsoft/vscode-spell-check')); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.Source' && + p.Value === 'https://server.com/Microsoft/vscode-spell-check.git' + ) + ); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.Getstarted' && + p.Value === 'https://server.com/Microsoft/vscode-spell-check.git' + ) + ); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.Repository' && + p.Value === 'https://server.com/Microsoft/vscode-spell-check.git' + ) + ); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.Support' && + p.Value === 'https://server.com/Microsoft/vscode-spell-check/issues' + ) + ); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.Learn' && + p.Value === 'https://server.com/Microsoft/vscode-spell-check' + ) + ); }); }); @@ -654,16 +773,22 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), repository: { - type: "git", - url: "https://github.com/Microsoft/vscode-spell-check.git" - } + type: 'git', + url: 'https://github.com/Microsoft/vscode-spell-check.git', + }, }; return _toVsixManifest(manifest, []) .then(xml => parseXmlManifest(xml)) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property.map(p => p.$); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.GitHub' && p.Value === 'https://github.com/Microsoft/vscode-spell-check.git')); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.GitHub' && + p.Value === 'https://github.com/Microsoft/vscode-spell-check.git' + ) + ); assert.ok(properties.every(p => p.Id !== 'Microsoft.VisualStudio.Services.Links.Repository')); }); }); @@ -674,14 +799,20 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - repository: 'Microsoft/vscode-spell-check' + repository: 'Microsoft/vscode-spell-check', }; return _toVsixManifest(manifest, []) .then(xml => parseXmlManifest(xml)) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property.map(p => p.$); - assert.ok(properties.some(p => p.Id === 'Microsoft.VisualStudio.Services.Links.GitHub' && p.Value === 'https://github.com/Microsoft/vscode-spell-check.git')); + assert.ok( + properties.some( + p => + p.Id === 'Microsoft.VisualStudio.Services.Links.GitHub' && + p.Value === 'https://github.com/Microsoft/vscode-spell-check.git' + ) + ); assert.ok(properties.every(p => p.Id !== 'Microsoft.VisualStudio.Services.Links.Repository')); }); }); @@ -692,7 +823,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - categories: ['hello', 'world'] + categories: ['hello', 'world'], }; return _toVsixManifest(manifest, []) @@ -710,7 +841,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - preview: true + preview: true, }; return _toVsixManifest(manifest, []) @@ -727,8 +858,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }] - } + themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }], + }, }; return _toVsixManifest(manifest, []) @@ -746,8 +877,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - themes: [] - } + themes: [], + }, }; return _toVsixManifest(manifest, []) @@ -762,8 +893,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }] - } + themes: [{ label: 'monokai', uiTheme: 'vs', path: 'monokai.tmTheme' }], + }, }; return _toVsixManifest(manifest, []) @@ -781,8 +912,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - iconThemes: [{ id: 'fakeicons', label: 'fakeicons', path: 'fake.icons' }] - } + iconThemes: [{ id: 'fakeicons', label: 'fakeicons', path: 'fake.icons' }], + }, }; return _toVsixManifest(manifest, []) @@ -800,8 +931,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - iconThemes: [{ id: 'fakeicons', label: 'fakeicons', path: 'fake.icons' }] - } + iconThemes: [{ id: 'fakeicons', label: 'fakeicons', path: 'fake.icons' }], + }, }; return _toVsixManifest(manifest, []) @@ -818,7 +949,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - activationEvents: ['onLanguage:go'] + activationEvents: ['onLanguage:go'], }; return _toVsixManifest(manifest, []) @@ -833,8 +964,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - languages: [{ id: 'go' }] - } + languages: [{ id: 'go' }], + }, }; return _toVsixManifest(manifest, []) @@ -849,8 +980,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - snippets: [{ language: 'go', path: 'gosnippets.json' }] - } + snippets: [{ language: 'go', path: 'gosnippets.json' }], + }, }; return _toVsixManifest(manifest, []) @@ -864,7 +995,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - keywords: ['theme', 'theme'] + keywords: ['theme', 'theme'], }; return _toVsixManifest(manifest, []) @@ -879,10 +1010,8 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - keybindings: [ - { command: 'hello', 'key': 'ctrl+f1' } - ] - } + keybindings: [{ command: 'hello', key: 'ctrl+f1' }], + }, }; return _toVsixManifest(manifest, []) @@ -900,14 +1029,16 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - debuggers: [{ - type: "node", - label: "Node Debug", - program: "./out/node/nodeDebug.js", - runtime: "node", - enableBreakpointsFor: { "languageIds": ["javascript", "javascriptreact"] } - }] - } + debuggers: [ + { + type: 'node', + label: 'Node Debug', + program: './out/node/nodeDebug.js', + runtime: 'node', + enableBreakpointsFor: { languageIds: ['javascript', 'javascriptreact'] }, + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -925,11 +1056,13 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - jsonValidation: [{ - fileMatch: ".jshintrc", - url: "http://json.schemastore.org/jshintrc" - }] - } + jsonValidation: [ + { + fileMatch: '.jshintrc', + url: 'http://json.schemastore.org/jshintrc', + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -946,16 +1079,25 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - description: 'This C++ extension likes combines ftp with javascript' + description: 'This C++ extension likes combines ftp with javascript', }; return _toVsixManifest(manifest, []) .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'c++'), 'detect c++'); - assert(tags.some(tag => tag === 'ftp'), 'detect ftp'); - assert(tags.some(tag => tag === 'javascript'), 'detect javascript'); + assert( + tags.some(tag => tag === 'c++'), + 'detect c++' + ); + assert( + tags.some(tag => tag === 'ftp'), + 'detect ftp' + ); + assert( + tags.some(tag => tag === 'javascript'), + 'detect javascript' + ); assert(!_.includes(tags, 'java'), "don't detect java"); }); }); @@ -967,12 +1109,14 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - grammars: [{ - language: "shellscript", - scopeName: "source.shell", - path: "./syntaxes/Shell-Unix-Bash.tmLanguage" - }] - } + grammars: [ + { + language: 'shellscript', + scopeName: 'source.shell', + path: './syntaxes/Shell-Unix-Bash.tmLanguage', + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -990,11 +1134,13 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - languages: [{ - id: 'go', - aliases: ['golang', 'google-go'] - }] - } + languages: [ + { + id: 'go', + aliases: ['golang', 'google-go'], + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -1014,11 +1160,16 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - localizations: [{ - languageId: 'de', - translations: [{ id: 'vscode', path: 'fake.json' }, { id: 'vscode.go', path: 'what.json' }] - }] - } + localizations: [ + { + languageId: 'de', + translations: [ + { id: 'vscode', path: 'fake.json' }, + { id: 'vscode.go', path: 'what.json' }, + ], + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -1046,32 +1197,41 @@ describe('toVsixManifest', () => { languageName: 'German', translations: [ { id: 'vscode', path: 'de.json' }, - { id: 'vscode.go', path: 'what.json' } - ] + { id: 'vscode.go', path: 'what.json' }, + ], }, { languageId: 'pt', languageName: 'Portuguese', localizedLanguageName: 'Português', - translations: [ - { id: 'vscode', path: './translations/pt.json' } - ] - } - ] - } + translations: [{ id: 'vscode', path: './translations/pt.json' }], + }, + ], + }, }; const files = [ { path: 'extension/de.json', contents: Buffer.from('') }, - { path: 'extension/translations/pt.json', contents: Buffer.from('') } + { path: 'extension/translations/pt.json', contents: Buffer.from('') }, ]; return _toVsixManifest(manifest, files) .then(parseXmlManifest) .then(result => { const assets = result.PackageManifest.Assets[0].Asset; - assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.DE' && asset.$.Path === 'extension/de.json')); - assert(assets.some(asset => asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.PT' && asset.$.Path === 'extension/translations/pt.json')); + assert( + assets.some( + asset => + asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.DE' && asset.$.Path === 'extension/de.json' + ) + ); + assert( + assets.some( + asset => + asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.PT' && + asset.$.Path === 'extension/translations/pt.json' + ) + ); const properties = result.PackageManifest.Metadata[0].Properties[0].Property; const localizedLangProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.LocalizedLanguages'); @@ -1091,11 +1251,13 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - languages: [{ - id: 'go', - extensions: ['go', 'golang'] - }] - } + languages: [ + { + id: 'go', + extensions: ['go', 'golang'], + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -1114,11 +1276,13 @@ describe('toVsixManifest', () => { version: '0.0.1', engines: Object.create(null), contributes: { - languages: [{ - id: 'go', - extensions: ['.go'] - }] - } + languages: [ + { + id: 'go', + extensions: ['.go'], + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -1137,8 +1301,8 @@ describe('toVsixManifest', () => { engines: Object.create(null), badges: [ { url: 'http://badgeurl.png', href: 'http://badgeurl', description: 'this is a badge' }, - { url: 'http://anotherbadgeurl.png', href: 'http://anotherbadgeurl', description: 'this is another badge' } - ] + { url: 'http://anotherbadgeurl.png', href: 'http://anotherbadgeurl', description: 'this is another badge' }, + ], }; return _toVsixManifest(manifest, []) @@ -1161,19 +1325,19 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', engines: Object.create(null), - "contributes": { - "grammars": [ + contributes: { + grammars: [ { - "language": "javascript", - "scopeName": "source.js.jsx", - "path": "./syntaxes/Babel Language.json" + language: 'javascript', + scopeName: 'source.js.jsx', + path: './syntaxes/Babel Language.json', }, { - "scopeName": "source.regexp.babel", - "path": "./syntaxes/Babel Regex.json" - } - ] - } + scopeName: 'source.regexp.babel', + path: './syntaxes/Babel Regex.json', + }, + ], + }, }; return _toVsixManifest(manifest, []) @@ -1190,7 +1354,7 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: { vscode: '^1.0.0' } as any + engines: { vscode: '^1.0.0' } as any, }; return _toVsixManifest(manifest, []) @@ -1211,14 +1375,18 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; return _toVsixManifest(manifest, []) .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert(properties.some(p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true')); + assert( + properties.some( + p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true' + ) + ); }); }); @@ -1229,14 +1397,18 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', markdown: 'standard' as 'standard', - engines: Object.create(null) + engines: Object.create(null), }; return _toVsixManifest(manifest, []) .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert(properties.some(p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'false')); + assert( + properties.some( + p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'false' + ) + ); }); }); @@ -1247,14 +1419,18 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', markdown: 'wow' as any, - engines: Object.create(null) + engines: Object.create(null), }; return _toVsixManifest(manifest, []) .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert(properties.some(p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true')); + assert( + properties.some( + p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true' + ) + ); }); }); @@ -1265,11 +1441,7 @@ describe('toVsixManifest', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - extensionDependencies: [ - "foo.bar", - "foo.bar", - "monkey.hello" - ] + extensionDependencies: ['foo.bar', 'foo.bar', 'monkey.hello'], }; return _toVsixManifest(manifest, []) @@ -1292,12 +1464,12 @@ describe('toVsixManifest', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; const files = [ - { path: 'extension/file.txt' }, - { path: 'extension/FILE.txt' }, + { path: 'extension/file.txt', contents: '' }, + { path: 'extension/FILE.txt', contents: '' }, ]; try { @@ -1310,84 +1482,190 @@ describe('toVsixManifest', () => { throw new Error('Should not reach here'); }); - describe('qna', () => { - it('should use marketplace qna by default', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null) - }); + it('should expose web extension assets and properties', async () => { + const manifest = createManifest({ + browser: 'browser.js', + extensionKind: ['web'], + }); + const files = [{ path: 'extension/browser.js', contents: Buffer.from('') }]; + + const vsixManifest = await _toVsixManifest(manifest, files, { web: true }); + const result = await parseXmlManifest(vsixManifest); + const assets = result.PackageManifest.Assets[0].Asset; + assert( + assets.some( + asset => + asset.$.Type === 'Microsoft.VisualStudio.Code.WebResources/extension/browser.js' && + asset.$.Path === 'extension/browser.js' + ) + ); + + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension'); + assert.equal(webExtensionProps.length, 1); + assert.equal(webExtensionProps[0].$.Value, 'true'); + }); + + it('should expose web extension assets and properties when extension kind is not provided', async () => { + const manifest = createManifest({ + browser: 'browser.js', + }); + const files = [{ path: 'extension/browser.js', contents: Buffer.from('') }]; + + const vsixManifest = await _toVsixManifest(manifest, files, { web: true }); + const result = await parseXmlManifest(vsixManifest); + const assets = result.PackageManifest.Assets[0].Asset; + assert( + assets.some( + asset => + asset.$.Type === 'Microsoft.VisualStudio.Code.WebResources/extension/browser.js' && + asset.$.Path === 'extension/browser.js' + ) + ); + + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension'); + assert.equal(webExtensionProps.length, 1); + assert.equal(webExtensionProps[0].$.Value, 'true'); + }); + + it('should not expose web extension assets and properties for web extension when not asked for', async () => { + const manifest = createManifest({ + browser: 'browser.js', + extensionKind: ['web'], + }); + const files = [{ path: 'extension/browser.js', contents: Buffer.from('') }]; + + const vsixManifest = await _toVsixManifest(manifest, files); + const result = await parseXmlManifest(vsixManifest); + const assets = result.PackageManifest.Assets[0].Asset; + assert(assets.every(asset => !asset.$.Type.startsWith('Microsoft.VisualStudio.Code.WebResources'))); + + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension'); + assert.equal(webExtensionProps.length, 0); + }); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + it('should not expose web extension assets and properties for non web extension', async () => { + const manifest = createManifest({ + main: 'main.js', }); + const files = [{ path: 'extension/main.js', contents: Buffer.from('') }]; - it('should not use marketplace in a github repo, without specifying it', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null), - repository: 'https://github.com/username/repository' - }); + const vsixManifest = await _toVsixManifest(manifest, files, { web: true }); + const result = await parseXmlManifest(vsixManifest); + const assets = result.PackageManifest.Assets[0].Asset; + assert(assets.every(asset => !asset.$.Type.startsWith('Microsoft.VisualStudio.Code.WebResources'))); + + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const webExtensionProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.WebExtension'); + assert.equal(webExtensionProps.length, 0); + }); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + it('should expose extension kind properties when providedd', async () => { + const manifest = createManifest({ + extensionKind: ['ui', 'workspace', 'web'], }); + const files = [{ path: 'extension/main.js', contents: Buffer.from('') }]; - it('should use marketplace in a github repo, when specifying it', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null), - repository: 'https://github.com/username/repository', - qna: 'marketplace' - }); + const vsixManifest = await _toVsixManifest(manifest, files, { web: true }); + const result = await parseXmlManifest(vsixManifest); + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const extensionKindProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionKind'); + assert.equal(extensionKindProps[0].$.Value, ['ui', 'workspace', 'web'].join(',')); + }); - assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true'); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + it('should expose extension kind properties when derived', async () => { + const manifest = createManifest({ + main: 'main.js', }); + const files = [{ path: 'extension/main.js', contents: Buffer.from('') }]; - it('should handle qna=marketplace', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null), - qna: 'marketplace' - }); + const vsixManifest = await _toVsixManifest(manifest, files, { web: true }); + const result = await parseXmlManifest(vsixManifest); + const properties = result.PackageManifest.Metadata[0].Properties[0].Property; + const extensionKindProps = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionKind'); + assert.equal(extensionKindProps[0].$.Value, 'workspace'); + }); +}); - assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true'); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); +describe('qna', () => { + it('should use marketplace qna by default', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), }); - it('should handle qna=false', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null), - qna: false - }); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + }); - assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'false'); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + it('should not use marketplace in a github repo, without specifying it', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', }); - it('should handle custom qna', async () => { - const xmlManifest = await toXMLManifest({ - name: 'test', - publisher: 'mocha', - version: '0.0.1', - engines: Object.create(null), - qna: 'http://myqna' - }); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + }); + + it('should use marketplace in a github repo, when specifying it', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + qna: 'marketplace', + }); + + assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true'); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + }); + + it('should handle qna=marketplace', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + qna: 'marketplace', + }); - assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); - assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink', 'http://myqna'); + assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'true'); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + }); + + it('should handle qna=false', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + qna: false, + }); + + assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA', 'false'); + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink'); + }); + + it('should handle custom qna', async () => { + const xmlManifest = await toXMLManifest({ + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + qna: 'http://myqna', }); + + assertMissingProperty(xmlManifest, 'Microsoft.VisualStudio.Services.EnableMarketplaceQnA'); + assertProperty(xmlManifest, 'Microsoft.VisualStudio.Services.CustomerQnALink', 'http://myqna'); }); }); @@ -1407,40 +1685,48 @@ describe('toContentTypes', () => { it('should include extra extensions', () => { const files = [ - { path: 'hello.txt' }, - { path: 'hello.png' }, - { path: 'hello.md' }, - { path: 'hello' } + { path: 'hello.txt', contents: '' }, + { path: 'hello.png', contents: '' }, + { path: 'hello.md', contents: '' }, + { path: 'hello', contents: '' }, ]; return toContentTypes(files) .then(xml => parseContentTypes(xml)) .then(result => { assert.ok(result.Types.Default, 'there are content types'); - assert.ok(result.Types.Default.some(d => d.$.Extension === '.txt' && d.$.ContentType === 'text/plain'), 'there are txt'); - assert.ok(result.Types.Default.some(d => d.$.Extension === '.png' && d.$.ContentType === 'image/png'), 'there are png'); - assert.ok(result.Types.Default.some(d => d.$.Extension === '.md' && /^text\/(x-)?markdown$/.test(d.$.ContentType)), 'there are md'); + assert.ok( + result.Types.Default.some(d => d.$.Extension === '.txt' && d.$.ContentType === 'text/plain'), + 'there are txt' + ); + assert.ok( + result.Types.Default.some(d => d.$.Extension === '.png' && d.$.ContentType === 'image/png'), + 'there are png' + ); + assert.ok( + result.Types.Default.some(d => d.$.Extension === '.md' && /^text\/(x-)?markdown$/.test(d.$.ContentType)), + 'there are md' + ); assert.ok(!result.Types.Default.some(d => d.$.Extension === '')); }); }); }); describe('MarkdownProcessor', () => { - it('should throw when no baseContentUrl is provided', async () => { const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.md') + localPath: path.join(root, 'readme.md'), }; let didThrow = false; @@ -1460,26 +1746,26 @@ describe('MarkdownProcessor', () => { publisher: 'mocha', version: '0.0.1', description: 'test extension', - engines: Object.create(null) + engines: Object.create(null), }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, { baseContentUrl: 'https://github.com/username/repository/blob/master', - baseImagesUrl: 'https://github.com/username/repository/raw/master' + baseImagesUrl: 'https://github.com/username/repository/raw/master', }); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.md') + localPath: path.join(root, 'readme.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.expected.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1490,23 +1776,113 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://github.com/username/repository' + repository: 'https://github.com/username/repository', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.md') + localPath: path.join(root, 'readme.md'), + }; + + return processor + .onFile(readme) + .then(file => read(file)) + .then(actual => { + return readFile(path.join(root, 'readme.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); + }); + }); + + it('should replace relative links with GitHub URLs while respecting githubBranch', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + description: 'test extension', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; + + const root = fixture('readme'); + const processor = new ReadmeProcessor(manifest, { + githubBranch: 'main', + }); + const readme = { + path: 'extension/readme.md', + localPath: path.join(root, 'readme.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.expected.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.branch.main.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); + }); + }); + + it('should override image URLs with baseImagesUrl while also respecting githubBranch', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + description: 'test extension', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; + + const root = fixture('readme'); + const processor = new ReadmeProcessor(manifest, { + githubBranch: 'main', + // Override image relative links to point to different base URL + baseImagesUrl: 'https://github.com/base', + }); + const readme = { + path: 'extension/readme.md', + localPath: path.join(root, 'readme.md'), + }; + + return processor + .onFile(readme) + .then(file => read(file)) + .then(actual => { + return readFile(path.join(root, 'readme.branch.override.images.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); + }); + }); + + it('should override githubBranch setting with baseContentUrl', () => { + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + description: 'test extension', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; + + const root = fixture('readme'); + const processor = new ReadmeProcessor(manifest, { + githubBranch: 'main', + baseContentUrl: 'https://github.com/base', + }); + const readme = { + path: 'extension/readme.md', + localPath: path.join(root, 'readme.md'), + }; + + return processor + .onFile(readme) + .then(file => read(file)) + .then(actual => { + return readFile(path.join(root, 'readme.branch.override.content.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1517,23 +1893,23 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://github.com/username/repository.git' + repository: 'https://github.com/username/repository.git', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.md') + localPath: path.join(root, 'readme.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.expected.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1544,27 +1920,27 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://github.com/username/repository.git' + repository: 'https://github.com/username/repository.git', }; const options = { - baseImagesUrl: 'https://github.com/username/repository/path/to' + baseImagesUrl: 'https://github.com/username/repository/path/to', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, options); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.md') + localPath: path.join(root, 'readme.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.images.expected.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.images.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1575,23 +1951,23 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://github.com/username/repository.git' + repository: 'https://github.com/username/repository.git', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.github.md') + localPath: path.join(root, 'readme.github.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.github.expected.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.github.expected.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1602,23 +1978,23 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://github.com/username/repository.git' + repository: 'https://github.com/username/repository.git', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, { expandGitHubIssueLinks: false }); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.github.md') + localPath: path.join(root, 'readme.github.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.github.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.github.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); @@ -1629,28 +2005,34 @@ describe('MarkdownProcessor', () => { version: '0.0.1', description: 'test extension', engines: Object.create(null), - repository: 'https://some-other-provider.com/username/repository.git' + repository: 'https://some-other-provider.com/username/repository.git', }; const root = fixture('readme'); const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', - localPath: path.join(root, 'readme.github.md') + localPath: path.join(root, 'readme.github.md'), }; - return processor.onFile(readme) + return processor + .onFile(readme) .then(file => read(file)) .then(actual => { - return readFile(path.join(root, 'readme.github.md'), 'utf8') - .then(expected => { - assert.equal(actual, expected); - }); + return readFile(path.join(root, 'readme.github.md'), 'utf8').then(expected => { + assert.equal(actual, expected); + }); }); }); it('should prevent non-HTTPS images', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `![title](http://foo.png)`; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1659,7 +2041,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent non-HTTPS img tags', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = ``; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1668,7 +2056,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent SVGs from not trusted sources', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `![title](https://foo/hello.svg)`; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1677,7 +2071,13 @@ describe('MarkdownProcessor', () => { }); it('should allow SVGs from trusted sources', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `![title](https://badges.gitter.im/hello.svg)`; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1687,7 +2087,13 @@ describe('MarkdownProcessor', () => { }); it('should allow SVG from GitHub actions in image tag', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `![title](https://github.com/fakeuser/fakerepo/workflows/fakeworkflowname/badge.svg)`; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1697,7 +2103,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent SVG from a GitHub repo in image tag', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `![title](https://github.com/eviluser/evilrepo/blob/master/malicious.svg)`; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1706,7 +2118,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent SVGs from not trusted sources in img tags', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = ``; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1715,7 +2133,13 @@ describe('MarkdownProcessor', () => { }); it('should allow SVGs from trusted sources in img tags', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = ``; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1725,7 +2149,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent SVG tags', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = ``; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1734,7 +2164,13 @@ describe('MarkdownProcessor', () => { }); it('should prevent SVG data urls in img tags', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = ``; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; @@ -1743,11 +2179,185 @@ describe('MarkdownProcessor', () => { }); it('should catch an unchanged README.md', async () => { - const manifest = { name: 'test', publisher: 'mocha', version: '0.0.1', engines: Object.create(null), repository: 'https://github.com/username/repository' }; + const manifest = { + name: 'test', + publisher: 'mocha', + version: '0.0.1', + engines: Object.create(null), + repository: 'https://github.com/username/repository', + }; const contents = `This is the README for your extension `; const processor = new ReadmeProcessor(manifest, {}); const readme = { path: 'extension/readme.md', contents }; await throws(() => processor.onFile(readme)); - }) + }); +}); + +describe('isSupportedWebExtension', () => { + it('should return true if extension report has extension', () => { + const manifest = createManifest({ name: 'test', publisher: 'mocha' }); + const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: ['mocha.test'], publishers: [] } }; + assert.ok(isSupportedWebExtension(manifest, extensionReport)); + }); + + it('should return true if extension report has publisher', () => { + const manifest = createManifest({ name: 'test', publisher: 'mocha' }); + const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: [], publishers: ['mocha'] } }; + assert.ok(isSupportedWebExtension(manifest, extensionReport)); + }); + + it('should return false if extension report does not has extension', () => { + const manifest = createManifest({ name: 'test', publisher: 'mocha' }); + const extensionReport: IExtensionsReport = { malicious: [], web: { extensions: [], publishers: [] } }; + assert.ok(!isSupportedWebExtension(manifest, extensionReport)); + }); +}); + +describe('WebExtensionProcessor', () => { + it('should include file', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + const file = { path: 'extension/browser.js', contents: '' }; + + await processor.onFile(file); + await processor.onEnd(); + + const expected: IAsset[] = [{ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path }]; + assert.deepEqual(processor.assets, expected); + }); + + it('should include file when extension kind is not specified', async () => { + const manifest = createManifest({ browser: 'browser.js' }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + const file = { path: 'extension/browser.js', contents: '' }; + + await processor.onFile(file); + await processor.onEnd(); + + const expected: IAsset[] = [{ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path }]; + assert.deepEqual(processor.assets, expected); + }); + + it('should not include file when not asked for', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: false }); + const file = { path: 'extension/browser.js', contents: '' }; + + await processor.onFile(file); + await processor.onEnd(); + + assert.deepEqual(processor.assets, []); + }); + + it('should not include file for non web extension', async () => { + const manifest = createManifest({ extensionKind: ['ui'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + const file = { path: 'extension/browser.js', contents: '' }; + + await processor.onFile(file); + await processor.onEnd(); + + assert.deepEqual(processor.assets, []); + }); + + it('should include manifest', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + const manifestFile = { path: 'extension/package.json', contents: JSON.stringify(manifest) }; + + await processor.onFile(manifestFile); + await processor.onEnd(); + + const expected: IAsset[] = [ + { type: `Microsoft.VisualStudio.Code.WebResources/${manifestFile.path}`, path: manifestFile.path }, + ]; + assert.deepEqual(processor.assets, expected); + }); + + it('should fail for svg file', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + try { + await processor.onFile({ path: 'extension/sample.svg', contents: '' }); + } catch (error) { + return; // expected + } + + assert.fail('Should fail'); + }); + + it('should include max 25 files', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + const expected: IAsset[] = []; + for (let i = 1; i <= 25; i++) { + const file = { path: `extension/${i}.json`, contents: `${i}` }; + await processor.onFile(file); + expected.push({ type: `Microsoft.VisualStudio.Code.WebResources/${file.path}`, path: file.path }); + } + + await processor.onEnd(); + + assert.deepEqual(processor.assets.length, 25); + assert.deepEqual(processor.assets, expected); + }); + + it('should throw an error if there are more than 25 files', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + for (let i = 1; i <= 26; i++) { + await processor.onFile({ path: `extension/${i}.json`, contents: `${i}` }); + } + + try { + await processor.onEnd(); + } catch (error) { + return; // expected error + } + assert.fail('Should fail'); + }); + + it('should include web extension property & tag', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + await processor.onEnd(); + + assert.equal(processor.vsix.webExtension, true); + assert.deepEqual(processor.tags, ['__web_extension']); + }); + + it('should include web extension property & tag when extension kind is not provided', async () => { + const manifest = createManifest({ browser: 'browser.js' }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + await processor.onEnd(); + + assert.equal(processor.vsix.webExtension, true); + assert.deepEqual(processor.tags, ['__web_extension']); + }); + + it('should not include web extension property & tag when not asked for', async () => { + const manifest = createManifest({ extensionKind: ['web'] }); + const processor = new WebExtensionProcessor(manifest, { web: false }); + + await processor.onEnd(); + + assert.equal(processor.vsix.webExtension, undefined); + assert.deepEqual(processor.tags, []); + }); + + it('should not include web extension property & tag for non web extension', async () => { + const manifest = createManifest({ extensionKind: ['ui'] }); + const processor = new WebExtensionProcessor(manifest, { web: true }); + + await processor.onEnd(); + + assert.equal(processor.vsix.webExtension, undefined); + assert.deepEqual(processor.tags, []); + }); }); diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index b98a3d9b1..f7d304197 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -1,5 +1,11 @@ import * as assert from 'assert'; -import { validatePublisher, validateExtensionName, validateVersion, validateEngineCompatibility, validateVSCodeTypesCompatibility } from '../validation'; +import { + validatePublisher, + validateExtensionName, + validateVersion, + validateEngineCompatibility, + validateVSCodeTypesCompatibility, +} from '../validation'; describe('validatePublisher', () => { it('should throw with empty', () => { @@ -101,7 +107,6 @@ describe('validateEngineCompatibility', () => { }); describe('validateVSCodeTypesCompatibility', () => { - it('should validate', () => { validateVSCodeTypesCompatibility('*', '1.30.0'); validateVSCodeTypesCompatibility('*', '^1.30.0'); @@ -126,4 +131,4 @@ describe('validateVSCodeTypesCompatibility', () => { assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30.0')); assert.throws(() => validateVSCodeTypesCompatibility('1.5', '1.30')); }); -}); \ No newline at end of file +}); diff --git a/src/util.ts b/src/util.ts index d9968cecb..da791d208 100644 --- a/src/util.ts +++ b/src/util.ts @@ -67,10 +67,11 @@ export function isCancelledError(error: any) { } export class CancellationToken { - private listeners: Function[] = []; private _cancelled: boolean = false; - get isCancelled(): boolean { return this._cancelled; } + get isCancelled(): boolean { + return this._cancelled; + } subscribe(fn: Function): Function { this.listeners.push(fn); @@ -105,7 +106,7 @@ enum LogMessageType { DONE, INFO, WARNING, - ERROR + ERROR, } const LogPrefix = { @@ -135,5 +136,5 @@ export const log = { done: _log.bind(null, LogMessageType.DONE) as LogFn, info: _log.bind(null, LogMessageType.INFO) as LogFn, warn: _log.bind(null, LogMessageType.WARNING) as LogFn, - error: _log.bind(null, LogMessageType.ERROR) as LogFn -}; \ No newline at end of file + error: _log.bind(null, LogMessageType.ERROR) as LogFn, +}; diff --git a/src/validation.ts b/src/validation.ts index 3b0cec75d..c16fdf83a 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -5,11 +5,15 @@ const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i; export function validatePublisher(publisher: string): void { if (!publisher) { - throw new Error(`Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions`); + 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`); + 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` + ); } } @@ -44,7 +48,7 @@ export function validateEngineCompatibility(version: string): void { } /** - * User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode + * User shouldn't use a newer version of @types/vscode than the one specified in engines.vscode */ export function validateVSCodeTypesCompatibility(engineVersion: string, typeVersion: string): void { if (engineVersion === '*') { @@ -89,7 +93,9 @@ export function validateVSCodeTypesCompatibility(engineVersion: string, typeVers } }); - const error = new Error(`@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Consider upgrade engines.vscode or use an older @types/vscode version`); + const error = new Error( + `@types/vscode ${typeVersion} greater than engines.vscode ${engineVersion}. Consider upgrade engines.vscode or use an older @types/vscode version` + ); if (typeMajor > engineMajor) { throw error; @@ -100,4 +106,4 @@ export function validateVSCodeTypesCompatibility(engineVersion: string, typeVers if (typeMajor === engineMajor && typeMinor === engineMinor && typePatch > enginePatch) { throw error; } -} \ No newline at end of file +} diff --git a/src/viewutils.ts b/src/viewutils.ts index 26366e6ef..539dad3cc 100644 --- a/src/viewutils.ts +++ b/src/viewutils.ts @@ -14,12 +14,18 @@ const columns = process.stdout.columns ? process.stdout.columns : 80; const useFallbackIcons = process.platform === 'win32'; export const icons = useFallbackIcons - ? { download: '\u{2193}', star: '\u{2665}', emptyStar: '\u{2022}', } - : { download: '\u{2913}', star: '\u{2605}', emptyStar: '\u{2606}', }; + ? { download: '\u{2193}', star: '\u{2665}', emptyStar: '\u{2022}' } + : { download: '\u{2913}', star: '\u{2605}', emptyStar: '\u{2606}' }; -export function formatDate(date) { return date.toLocaleString(fixedLocale, format.date); } -export function formatTime(date) { return date.toLocaleString(fixedLocale, format.time); } -export function formatDateTime(date) { return date.toLocaleString(fixedLocale, { ...format.date, ...format.time }); } +export function formatDate(date) { + return date.toLocaleString(fixedLocale, format.date); +} +export function formatTime(date) { + return date.toLocaleString(fixedLocale, format.time); +} +export function formatDateTime(date) { + return date.toLocaleString(fixedLocale, { ...format.date, ...format.time }); +} export function repeatString(text: string, count: number): string { let result: string = ''; @@ -36,8 +42,10 @@ export function ratingStars(rating: number, total = 5): string { export function tableView(table: ViewTable, spacing: number = 2): string[] { 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('')); + 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('') + ); } export function wordWrap(text: string, width: number = columns): string { @@ -46,17 +54,21 @@ export function wordWrap(text: string, width: number = columns): string { return text .replace(/^\s+/, '') .split('') - .reduce(([out, buffer, pos], ch) => { - const nl = pos === maxWidth ? `\n${indent}` : ''; - const newPos: number = nl ? 0 : +pos + 1; - return / |-|,|\./.test(ch) ? - [`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer + ch, newPos]; - }, [indent, '', 0]) + .reduce( + ([out, buffer, pos], ch) => { + const nl = pos === maxWidth ? `\n${indent}` : ''; + const newPos: number = nl ? 0 : +pos + 1; + return / |-|,|\./.test(ch) ? [`${out}${buffer}${ch}${nl}`, '', newPos] : [`${out}${nl}`, buffer + ch, newPos]; + }, + [indent, '', 0] + ) .slice(0, 2) .join(''); -}; +} -export function indentRow(row: string) { return ` ${row}`; }; +export function indentRow(row: string) { + return ` ${row}`; +} export function wordTrim(text: string, width: number = columns, indicator = '...') { if (text.length > width) { diff --git a/tsconfig.json b/tsconfig.json index 558af2dc6..7664aa226 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,5 @@ "noUnusedLocals": true, "noUnusedParameters": true }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index 52521a77f..8b1dc8bdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,37 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@types/cheerio@^0.22.1": version "0.22.10" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6" integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg== +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/denodeify@^1.2.31": version "1.2.31" resolved "https://registry.yarnpkg.com/@types/denodeify/-/denodeify-1.2.31.tgz#9a737b063bf1a8e3a63cc006cbbb0de601ce3584" @@ -61,6 +87,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.38.tgz#e05c201a668492e534b48102aca0294898f449f6" integrity sha512-EibsnbJerd0hBFaDjJStFrVbVBAtOy4dgL8zZFw0uOvPqzBAX59Ci8cgjg3+RgJIWhsB5A4c+pi+D4P9tQQh/A== +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + "@types/read@^0.0.28": version "0.0.28" resolved "https://registry.yarnpkg.com/@types/read/-/read-0.0.28.tgz#cd0be409b192c6119f17e67d434f4c6e9418f1b5" @@ -105,6 +136,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + anymatch@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" @@ -120,6 +159,21 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-differ@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" + integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + azure-devops-node-api@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-7.2.0.tgz#131d4e01cf12ebc6e45569b5e0c5c249e4114d6d" @@ -170,12 +224,17 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -184,6 +243,22 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + cheerio@^1.0.0-rc.1: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -211,6 +286,11 @@ chokidar@3.3.0: optionalDependencies: fsevents "~2.1.1" +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -227,16 +307,33 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + commander@^2.8.1: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -257,6 +354,26 @@ concurrently@^5.1.0: tree-kill "^1.2.2" yargs "^13.3.0" +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -352,6 +469,13 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -405,6 +529,21 @@ esprima@^4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +execa@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" + integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -426,6 +565,21 @@ find-up@3.0.0, find-up@^3.0.0: dependencies: locate-path "^3.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-versions@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" + integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== + dependencies: + semver-regex "^2.0.0" + find-yarn-workspace-root@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" @@ -460,6 +614,13 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-stream@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + glob-parent@~5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" @@ -489,6 +650,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.0, has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" @@ -523,6 +689,40 @@ htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.0.6" +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +husky@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" + integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== + dependencies: + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^7.0.0" + find-versions "^3.2.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^4.2.0" + please-upgrade-node "^3.2.0" + slash "^3.0.0" + which-pm-runs "^1.0.0" + +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +import-fresh@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -592,6 +792,11 @@ is-regex@^1.0.5: dependencies: has "^1.0.3" +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -604,6 +809,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" @@ -617,11 +827,21 @@ json-parse-better-errors@^1.0.1: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + linkify-it@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db" @@ -637,15 +857,17 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash@^4.15.0: - version "4.17.13" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93" - integrity sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA== +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" -lodash@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.15.0, lodash@^4.17.15: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== log-symbols@3.0.0: version "3.0.0" @@ -670,6 +892,11 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -683,6 +910,11 @@ mime@^1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -732,6 +964,11 @@ mocha@^7.1.1: yargs-parser "13.1.2" yargs-unparser "1.6.0" +mri@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" + integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== + ms@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -742,6 +979,17 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +multimatch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" + integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== + dependencies: + "@types/minimatch" "^3.0.3" + array-differ "^3.0.0" + array-union "^2.1.0" + arrify "^2.0.1" + minimatch "^3.0.4" + mute-stream@~0.0.4: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -770,6 +1018,13 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nth-check@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -805,13 +1060,25 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +opencollective-postinstall@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -842,6 +1109,13 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -849,11 +1123,25 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -862,6 +1150,16 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + parse-semver@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/parse-semver/-/parse-semver-1.1.1.tgz#9a4afd6df063dc4826f93fba4a99cf223f666cb8" @@ -881,16 +1179,31 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -906,6 +1219,45 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + +prettier@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5" + integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg== + +pretty-quick@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.0.2.tgz#7ed460f7e43a647b1044ad8b7f41a0c8a7f1c51c" + integrity sha512-4rWOs/Ifdkg7G/YX7Xbco4jZkuXPx445KdhuMI6REnl3nXRDb9+zysb29c76R59jsJzcnkcpAaGi8D/RjAVfSQ== + dependencies: + chalk "^3.0.0" + execa "^4.0.0" + find-up "^4.1.0" + ignore "^5.1.4" + mri "^1.1.5" + multimatch "^4.0.0" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + read-pkg@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" @@ -948,6 +1300,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" @@ -972,6 +1329,16 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" + integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== + "semver@2 || 3 || 4 || 5": version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" @@ -992,6 +1359,28 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + source-map-support@^0.4.2: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" @@ -1112,6 +1501,11 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -1138,6 +1532,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + tmp@0.0.29: version "0.0.29" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" @@ -1213,6 +1614,11 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + which@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -1220,6 +1626,13 @@ which@1.3.1: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -1259,7 +1672,12 @@ y18n@^4.0.0: resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yargs-parser@13.1.2, yargs-parser@^13.1.2: +yaml@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yargs-parser@13.1.2, yargs-parser@^13.1.1, yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== @@ -1267,14 +1685,6 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f"