From 396a66f3539f6e913d8e440a3d6a8e950fbeddb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 4 Nov 2021 16:06:25 +0100 Subject: [PATCH] chore(engineering): strict typescript rules --- package-lock.json | 103 ++++------- package.json | 7 +- src/main.ts | 9 +- src/npm.ts | 25 ++- src/package.ts | 166 ++++++++--------- src/publicgalleryapi.ts | 6 +- src/publish.ts | 10 +- src/search.ts | 14 +- src/show.ts | 16 +- src/store.ts | 6 +- src/test/package.test.ts | 339 +++++++++++++++++----------------- src/test/validation.test.ts | 16 +- src/typings/parse-semver.d.ts | 9 + src/util.ts | 8 +- src/validation.ts | 2 +- src/viewutils.ts | 12 +- src/xml.ts | 4 +- src/zip.ts | 11 +- tsconfig.json | 15 +- 19 files changed, 396 insertions(+), 382 deletions(-) create mode 100644 src/typings/parse-semver.d.ts diff --git a/package-lock.json b/package-lock.json index 6ece63c3..50fff302 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,13 +20,12 @@ "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.2.1", "typed-rest-client": "^1.8.4", - "url-join": "^1.1.0", + "url-join": "^4.0.1", "xml2js": "^0.4.23", "yauzl": "^2.3.1", "yazl": "^2.2.2" @@ -48,8 +47,10 @@ "@types/read": "^0.0.28", "@types/semver": "^6.0.0", "@types/tmp": "^0.2.2", + "@types/url-join": "^4.0.1", "@types/xml2js": "^0.4.4", "@types/yauzl": "^2.9.2", + "@types/yazl": "^2.4.2", "husky": "^7.0.4", "mocha": "^7.1.1", "npm-run-all": "^4.1.5", @@ -1123,12 +1124,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/@semantic-release/github/node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "dev": true - }, "node_modules/@semantic-release/npm": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-8.0.2.tgz", @@ -1417,6 +1412,12 @@ "integrity": "sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==", "dev": true }, + "node_modules/@types/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==", + "dev": true + }, "node_modules/@types/xml2js": { "version": "0.4.4", "resolved": "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.4.tgz", @@ -1435,6 +1436,15 @@ "@types/node": "*" } }, + "node_modules/@types/yazl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-2.4.2.tgz", + "integrity": "sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -7731,31 +7741,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA= sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -9836,9 +9821,9 @@ } }, "node_modules/url-join": { - "version": "1.1.0", - "resolved": "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz", - "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -10911,12 +10896,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "dev": true } } }, @@ -11162,6 +11141,12 @@ "integrity": "sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==", "dev": true }, + "@types/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-wDXw9LEEUHyV+7UWy7U315nrJGJ7p1BzaCxDpEoLr789Dk1WDVMMlf3iBfbG2F8NdWnYyFbtTxUn2ZNbm1Q4LQ==", + "dev": true + }, "@types/xml2js": { "version": "0.4.4", "resolved": "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.4.tgz", @@ -11180,6 +11165,15 @@ "@types/node": "*" } }, + "@types/yazl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/yazl/-/yazl-2.4.2.tgz", + "integrity": "sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -15846,25 +15840,6 @@ "mimic-fn": "^2.1.0" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA= sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -17387,9 +17362,9 @@ "dev": true }, "url-join": { - "version": "1.1.0", - "resolved": "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz", - "integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" }, "util-deprecate": { "version": "1.0.2", diff --git a/package.json b/package.json index 70842419..97de9358 100644 --- a/package.json +++ b/package.json @@ -46,13 +46,12 @@ "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.2.1", "typed-rest-client": "^1.8.4", - "url-join": "^1.1.0", + "url-join": "^4.0.1", "xml2js": "^0.4.23", "yauzl": "^2.3.1", "yazl": "^2.2.2" @@ -71,8 +70,10 @@ "@types/read": "^0.0.28", "@types/semver": "^6.0.0", "@types/tmp": "^0.2.2", + "@types/url-join": "^4.0.1", "@types/xml2js": "^0.4.4", "@types/yauzl": "^2.9.2", + "@types/yazl": "^2.4.2", "husky": "^7.0.4", "mocha": "^7.1.1", "npm-run-all": "^4.1.5", @@ -87,7 +88,7 @@ "require": [ "ts-node/register" ], - "watch-files": "src/**,resources/**", + "watch-files": "src/**", "spec": "src/test/**/*.ts" }, "prettier": { diff --git a/src/main.ts b/src/main.ts index d2f29465..5e12b43d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ -import * as program from 'commander'; -import * as leven from 'leven'; - +import program from 'commander'; +import leven from 'leven'; import { packageCommand, ls } from './package'; import { publish, unpublish } from './publish'; import { show } from './show'; @@ -32,7 +31,7 @@ See https://code.visualstudio.com/api/working-with-extensions/publishing-extensi } function main(task: Promise): void { - let latestVersion: string = null; + let latestVersion: string | null = null; const token = new CancellationToken(); @@ -63,7 +62,7 @@ module.exports = function (argv: string[]): void { .description('Lists all the files that will be published') .option('--yarn', 'Use yarn instead of npm (default inferred from presence of yarn.lock or .yarnrc)') .option('--no-yarn', 'Use npm instead of yarn (default inferred from lack of yarn.lock or .yarnrc)') - .option( + .option( '--packagedDependencies ', 'Select packages that should be published only (includes dependencies)', (val, all) => (all ? all.concat(val) : [val]), diff --git a/src/npm.ts b/src/npm.ts index ea8173b7..a57eaad2 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -1,8 +1,8 @@ import * as path from 'path'; import * as fs from 'fs'; import * as cp from 'child_process'; -import * as parseSemver from 'parse-semver'; -import { CancellationToken, log } from './util'; +import parseSemver from 'parse-semver'; +import { CancellationToken, log, nonnull } from './util'; const exists = (file: string) => fs.promises.stat(file).then( @@ -30,7 +30,7 @@ function exec( cancellationToken?: CancellationToken ): Promise<{ stdout: string; stderr: string }> { return new Promise((c, e) => { - let disposeCancellationListener: Function = null; + let disposeCancellationListener: Function | null = null; const child = cp.exec(command, { ...options, encoding: 'utf8' } as any, (err, stdout: string, stderr: string) => { if (disposeCancellationListener) { @@ -45,7 +45,7 @@ function exec( }); if (cancellationToken) { - disposeCancellationListener = cancellationToken.subscribe(err => { + disposeCancellationListener = cancellationToken.subscribe((err: any) => { child.kill(); e(err); }); @@ -53,14 +53,13 @@ function exec( }); } -function checkNPM(cancellationToken?: CancellationToken): Promise { - return exec('npm -v', {}, cancellationToken).then(({ stdout }) => { - const version = stdout.trim(); +async function checkNPM(cancellationToken?: CancellationToken): Promise { + const { stdout } = await exec('npm -v', {}, cancellationToken); + const version = stdout.trim(); - if (/^3\.7\.[0123]$/.test(version)) { - return Promise.reject(`npm@${version} doesn't work with vsce. Please update npm: npm install -g npm`); - } - }); + if (/^3\.7\.[0123]$/.test(version)) { + throw new Error(`npm@${version} doesn't work with vsce. Please update npm: npm install -g npm`); + } } function getNpmDependencies(cwd: string): Promise { @@ -174,10 +173,10 @@ async function getYarnProductionDependencies(cwd: string, packagedDependencies?: let result = trees .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies)) - .filter(dep => !!dep); + .filter(nonnull); if (usingPackagedDependencies) { - result = selectYarnDependencies(result, packagedDependencies); + result = selectYarnDependencies(result, packagedDependencies!); } return result; diff --git a/src/package.ts b/src/package.ts index f40dc1d1..4b04276a 100644 --- a/src/package.ts +++ b/src/package.ts @@ -6,14 +6,14 @@ import * as yazl from 'yazl'; import { ExtensionKind, Manifest } from './manifest'; import { ITranslations, patchNLS } from './nls'; import * as util from './util'; -import * as glob from 'glob'; -import * as minimatch from 'minimatch'; -import * as markdownit from 'markdown-it'; +import glob from 'glob'; +import minimatch from 'minimatch'; +import markdownit from 'markdown-it'; import * as cheerio from 'cheerio'; import * as url from 'url'; -import { lookup } from 'mime'; +import mime from 'mime'; import * as semver from 'semver'; -import * as urljoin from 'url-join'; +import urljoin from 'url-join'; import { validatePublisher, validateExtensionName, @@ -23,7 +23,7 @@ import { } from './validation'; import { detectYarn, getDependencies } from './npm'; import * as GitHost from 'hosted-git-info'; -import * as parseSemver from 'parse-semver'; +import parseSemver from 'parse-semver'; const MinimatchOptions: minimatch.IOptions = { dot: true }; @@ -131,32 +131,28 @@ export class BaseProcessor implements IProcessor { assets: IAsset[] = []; tags: string[] = []; vsix: VSIX = Object.create(null); - onFile(file: IFile): Promise { - return Promise.resolve(file); + async onFile(file: IFile): Promise { + return file; } - onEnd() { - return Promise.resolve(null); + async onEnd() { + // noop } } // https://github.com/npm/cli/blob/latest/lib/utils/hosted-git-info-from-manifest.js -function getGitHost(manifest: Manifest): GitHost | null { +function getGitHost(manifest: Manifest): GitHost | undefined { const url = getRepositoryUrl(manifest); - - if (!url) { - return null; - } - - return GitHost.fromUrl(url, { noGitPlus: true }); + return url ? GitHost.fromUrl(url, { noGitPlus: true }) : undefined; } // https://github.com/npm/cli/blob/latest/lib/repo.js -function getRepositoryUrl(manifest: Manifest, gitHost?: GitHost | null): string | null { +function getRepositoryUrl(manifest: Manifest, gitHost?: GitHost | null): string | undefined { if (gitHost) { return gitHost.https(); } - let url: string | null = null; + let url: string | undefined = undefined; + if (manifest.repository) { if (typeof manifest.repository === 'string') { url = manifest.repository; @@ -173,7 +169,7 @@ function getRepositoryUrl(manifest: Manifest, gitHost?: GitHost | null): string } // https://github.com/npm/cli/blob/latest/lib/bugs.js -function getBugsUrl(manifest: Manifest, gitHost: GitHost | null): string | null { +function getBugsUrl(manifest: Manifest, gitHost: GitHost | undefined): string | undefined { if (manifest.bugs) { if (typeof manifest.bugs === 'string') { return manifest.bugs; @@ -190,11 +186,11 @@ function getBugsUrl(manifest: Manifest, gitHost: GitHost | null): string | null return gitHost.bugs(); } - return null; + return undefined; } // https://github.com/npm/cli/blob/latest/lib/docs.js -function getHomepageUrl(manifest: Manifest, gitHost: GitHost | null): string | null { +function getHomepageUrl(manifest: Manifest, gitHost: GitHost | undefined): string | undefined { if (manifest.homepage) { return manifest.homepage; } @@ -203,13 +199,13 @@ function getHomepageUrl(manifest: Manifest, gitHost: GitHost | null): string | n return gitHost.docs(); } - return null; + return undefined; } -// Contributed by Mozilla develpoer authors +// Contributed by Mozilla developer authors // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -function escapeRegExp(string) { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +function escapeRegExp(value: string) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } function toExtensionTags(extensions: string[]): string[] { @@ -273,20 +269,20 @@ const TrustedSVGSources = [ 'www.versioneye.com', ]; -function isGitHubRepository(repository: string | null): boolean { - return /^https:\/\/github\.com\/|^git@github\.com:/.test(repository || ''); +function isGitHubRepository(repository: string | undefined): boolean { + return /^https:\/\/github\.com\/|^git@github\.com:/.test(repository ?? ''); } -function isGitLabRepository(repository: string | null): boolean { - return /^https:\/\/gitlab\.com\/|^git@gitlab\.com:/.test(repository || ''); +function isGitLabRepository(repository: string | undefined): boolean { + return /^https:\/\/gitlab\.com\/|^git@gitlab\.com:/.test(repository ?? ''); } function isGitHubBadge(href: string): boolean { return /^https:\/\/github\.com\/[^/]+\/[^/]+\/(actions\/)?workflows\/.*badge\.svg/.test(href || ''); } -function isHostTrusted(url: url.UrlWithStringQuery): boolean { - return TrustedSVGSources.indexOf(url.host.toLowerCase()) > -1 || isGitHubBadge(url.href); +function isHostTrusted(url: url.URL): boolean { + return (url.host && TrustedSVGSources.indexOf(url.host.toLowerCase()) > -1) || isGitHubBadge(url.href); } export interface IVersionBumpOptions { @@ -310,7 +306,7 @@ export async function versionBump(options: IVersionBumpOptions): Promise { const manifest = await readManifest(cwd); if (manifest.version === options.version) { - return null; + return; } switch (options.version) { @@ -340,18 +336,12 @@ export async function versionBump(options: IVersionBumpOptions): Promise { command = `${command} --no-git-tag-version`; } - try { - // call `npm version` to do our dirty work - cp.exec; - const { stdout, stderr } = await promisify(cp.exec)(command, { cwd }); + // call `npm version` to do our dirty work + const { stdout, stderr } = await promisify(cp.exec)(command, { cwd }); - if (!process.env['VSCE_TESTS']) { - process.stdout.write(stdout); - process.stderr.write(stderr); - } - return null; - } catch (err) { - throw err.message; + if (!process.env['VSCE_TESTS']) { + process.stdout.write(stdout); + process.stderr.write(stderr); } } @@ -508,7 +498,7 @@ export class ManifestProcessor extends BaseProcessor { } export class TagsProcessor extends BaseProcessor { - private static Keywords = { + private static Keywords: Record = { git: ['git'], npm: ['node'], spell: ['markdown'], @@ -547,7 +537,7 @@ export class TagsProcessor extends BaseProcessor { rust: ['rust'], }; - onEnd(): Promise { + async onEnd(): Promise { const keywords = this.manifest.keywords ?? []; const contributes = this.manifest.contributes; const activationEvents = this.manifest.activationEvents ?? []; @@ -583,7 +573,7 @@ export class TagsProcessor extends BaseProcessor { const languageActivations = activationEvents .map(e => /^onLanguage:(.*)$/.exec(e)) - .filter(r => !!r) + .filter(util.nonnull) .map(r => r[1]); const grammars = ((contributes && contributes['grammars']) ?? []).map(g => g.language); @@ -618,17 +608,15 @@ export class TagsProcessor extends BaseProcessor { ]); this.tags = [...tags].filter(tag => !!tag); - - return Promise.resolve(null); } } export class MarkdownProcessor extends BaseProcessor { - private baseContentUrl: string; - private baseImagesUrl: string; + private baseContentUrl: string | undefined; + private baseImagesUrl: string | undefined; private isGitHub: boolean; private isGitLab: boolean; - private repositoryUrl: string; + private repositoryUrl: string | undefined; private gitHubIssueLinking: boolean; private gitLabIssueLinking: boolean; @@ -668,7 +656,7 @@ export class MarkdownProcessor extends BaseProcessor { } const markdownPathRegex = /(!?)\[([^\]\[]*|!\[[^\]\[]*]\([^\)]+\))\]\(([^\)]+)\)/g; - const urlReplace = (_, isImage, title, link: string) => { + const urlReplace = (_: string, isImage: string, title: string, link: string) => { if (/^mailto:/i.test(link)) { return `${isImage}[${title}](${link})`; } @@ -725,8 +713,8 @@ export class MarkdownProcessor extends BaseProcessor { issueNumber: string ): string => { let result = all; - let owner: string; - let repositoryName: string; + let owner: string | undefined; + let repositoryName: string | undefined; if (ownerAndRepositoryName) { [owner, repositoryName] = ownerAndRepositoryName.split('/', 2); @@ -738,7 +726,7 @@ export class MarkdownProcessor extends BaseProcessor { ? urljoin('https://github.com', owner, repositoryName, 'issues', issueNumber) : urljoin('https://gitlab.com', owner, repositoryName, '-', 'issues', issueNumber); result = prefix + `[${owner}/${repositoryName}#${issueNumber}](${issueUrl})`; - } else if (!owner && !repositoryName && issueNumber) { + } else if (!owner && !repositoryName && issueNumber && this.repositoryUrl) { // Issue in own repository result = prefix + @@ -759,10 +747,16 @@ export class MarkdownProcessor extends BaseProcessor { const $ = cheerio.load(html); $('img').each((_, img) => { - const src = decodeURI($(img).attr('src')); - const srcUrl = url.parse(src); + const rawSrc = $(img).attr('src'); + + if (!rawSrc) { + throw new Error(`Images in ${this.name} must have a source.`); + } + + const src = decodeURI(rawSrc); + const srcUrl = new url.URL(src); - if (/^data:$/i.test(srcUrl.protocol) && /^image$/i.test(srcUrl.host) && /\/svg/i.test(srcUrl.path)) { + if (/^data:$/i.test(srcUrl.protocol) && /^image$/i.test(srcUrl.host) && /\/svg/i.test(srcUrl.pathname)) { throw new Error(`SVG data URLs are not allowed in ${this.name}: ${src}`); } @@ -788,7 +782,9 @@ export class MarkdownProcessor extends BaseProcessor { } // GitHub heuristics - private guessBaseUrls(githostBranch: string | undefined): { content: string; images: string; repository: string } { + private guessBaseUrls( + githostBranch: string | undefined + ): { content: string; images: string; repository: string } | undefined { let repository = null; if (typeof this.manifest.repository === 'string') { @@ -798,7 +794,7 @@ export class MarkdownProcessor extends BaseProcessor { } if (!repository) { - return null; + return undefined; } const gitHubRegex = /(?github(\.com\/|:))(?(?:[^/]+)\/(?:[^/]+))(\/|$)/; @@ -808,7 +804,7 @@ export class MarkdownProcessor extends BaseProcessor { }; if (!match) { - return null; + return undefined; } const project = match.groups.project.replace(/\.git$/i, ''); @@ -828,7 +824,7 @@ export class MarkdownProcessor extends BaseProcessor { }; } - return null; + return undefined; } } @@ -902,13 +898,13 @@ class LicenseProcessor extends BaseProcessor { } class IconProcessor extends BaseProcessor { - private icon: string; + private icon: string | undefined; private didFindIcon = false; constructor(manifest: Manifest) { super(manifest); - this.icon = manifest.icon ? `extension/${manifest.icon}` : null; + this.icon = manifest.icon && `extension/${manifest.icon}`; delete this.vsix.icon; } @@ -922,12 +918,10 @@ class IconProcessor extends BaseProcessor { return Promise.resolve(file); } - onEnd(): Promise { + async onEnd(): Promise { if (this.icon && !this.didFindIcon) { return Promise.reject(new Error(`The specified icon '${this.icon}' wasn't found in the extension.`)); } - - return Promise.resolve(null); } } @@ -987,7 +981,7 @@ function deduceExtensionKinds(manifest: Manifest): ExtensionKind[] { let result: ExtensionKind[] = ['ui', 'workspace', 'web']; - const isNonEmptyArray = obj => Array.isArray(obj) && obj.length > 0; + const isNonEmptyArray = (obj: any) => Array.isArray(obj) && obj.length > 0; // Extension pack defaults to workspace,web extensionKind if (isNonEmptyArray(manifest.extensionPack) || isNonEmptyArray(manifest.extensionDependencies)) { result = ['workspace', 'web']; @@ -1078,7 +1072,7 @@ export class ValidationProcessor extends BaseProcessor { ]; for (const lower of this.duplicates) { - for (const filePath of this.files.get(lower)) { + for (const filePath of this.files.get(lower)!) { messages.push(` - ${filePath}`); } } @@ -1133,7 +1127,7 @@ export function validateManifest(manifest: Manifest): Manifest { (manifest.badges ?? []).forEach(badge => { const decodedUrl = decodeURI(badge.url); - const srcUrl = url.parse(decodedUrl); + const srcUrl = new url.URL(decodedUrl); if (!/^https:$/i.test(srcUrl.protocol)) { throw new Error(`Badge URLs must come from an HTTPS source: ${badge.url}`); @@ -1216,7 +1210,7 @@ const escapeChars = new Map([ ]); function escape(value: any): string { - return String(value).replace(/(['"<>&])/g, (_, char) => escapeChars.get(char)); + return String(value).replace(/(['"<>&])/g, (_, char) => escapeChars.get(char)!); } export async function toVsixManifest(vsix: VSIX): Promise { @@ -1330,7 +1324,7 @@ export async function toContentTypes(files: IFile[]): Promise { const ext = path.extname(file.path).toLowerCase(); if (ext) { - mimetypes.set(ext, lookup(ext)); + mimetypes.set(ext, mime.lookup(ext)); } } @@ -1380,20 +1374,19 @@ const defaultIgnore = [ const notIgnored = ['!package.json', '!README.md']; -function collectAllFiles( +async function collectAllFiles( cwd: string, dependencies: 'npm' | 'yarn' | 'none' | undefined, dependencyEntryPoints?: string[] ): Promise { - return getDependencies(cwd, dependencies, dependencyEntryPoints).then(deps => { - const promises: Promise[] = deps.map(dep => { - return promisify(glob)('**', { cwd: dep, nodir: true, dot: true, ignore: 'node_modules/**' }).then(files => - files.map(f => path.relative(cwd, path.join(dep, f))).map(f => f.replace(/\\/g, '/')) - ); - }); - - return Promise.all(promises).then(util.flatten); - }); + const deps = await getDependencies(cwd, dependencies, dependencyEntryPoints); + const promises = deps.map(dep => + promisify(glob)('**', { cwd: dep, nodir: true, dot: true, ignore: 'node_modules/**' }).then(files => + files.map(f => path.relative(cwd, path.join(dep, f))).map(f => f.replace(/\\/g, '/')) + ) + ); + + return Promise.all(promises).then(util.flatten); } function collectFiles( @@ -1432,7 +1425,10 @@ function collectFiles( // Split into ignore and negate list .then(ignore => - ignore.reduce((r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e]]), [[], []]) + ignore.reduce<[string[], string[]]>( + (r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e]]), + [[], []] + ) ) .then(r => ({ ignore: r[0], negate: r[1] })) @@ -1464,7 +1460,7 @@ export function processFiles(processors: IProcessor[], files: IFile[]): Promise< return r; }, new Set()), ].join(','); - const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags }); + const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags } as VSIX); return Promise.all([toVsixManifest(vsix), toContentTypes(files)]).then(result => { return [ diff --git a/src/publicgalleryapi.ts b/src/publicgalleryapi.ts index beed4471..c740c221 100644 --- a/src/publicgalleryapi.ts +++ b/src/publicgalleryapi.ts @@ -17,6 +17,10 @@ export interface ExtensionQuery { readonly assetTypes?: string[]; } +interface VSCodePublishedExtension extends PublishedExtension { + publisher: { displayName: string; publisherName: string }; +} + export class PublicGalleryAPI { private readonly client = new HttpClient('vsce'); @@ -32,7 +36,7 @@ export class PublicGalleryAPI { flags = [], criteria = [], assetTypes = [], - }: ExtensionQuery): Promise { + }: ExtensionQuery): Promise { const data = JSON.stringify({ filters: [{ pageNumber, pageSize, criteria }], assetTypes, diff --git a/src/publish.ts b/src/publish.ts index 0a4a5e0a..3e4293e4 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -99,16 +99,16 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter null, manifest.publisher, manifest.name, - null, + undefined, ExtensionQueryFlags.IncludeVersions ); - } catch (err) { + } catch (err: any) { if (err.statusCode !== 404) { throw err; } } - if (extension) { + if (extension && extension.versions) { const sameVersion = extension.versions.filter(v => v.version === manifest.version); if (sameVersion.length > 0) { @@ -127,7 +127,7 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter try { await api.updateExtension(undefined, packageStream, manifest.publisher, manifest.name); - } catch (err) { + } catch (err: any) { if (err.statusCode === 409) { throw new Error(`${description} already exists.`); } else { @@ -137,7 +137,7 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter } else { await api.createExtension(undefined, packageStream); } - } catch (err) { + } catch (err: any) { const message = (err && err.message) || ''; if (/Personal Access Token used has expired/.test(message)) { diff --git a/src/search.ts b/src/search.ts index 6897d1f8..8f8c6b83 100644 --- a/src/search.ts +++ b/src/search.ts @@ -1,14 +1,22 @@ import { getPublicGalleryAPI } from './util'; -import { ExtensionQueryFilterType, ExtensionQueryFlags } from 'azure-devops-node-api/interfaces/GalleryInterfaces'; +import { + ExtensionQueryFilterType, + ExtensionQueryFlags, + PublishedExtension, +} from 'azure-devops-node-api/interfaces/GalleryInterfaces'; import { tableView, wordTrim } from './viewutils'; const pageSize = 100; const installationTarget = 'Microsoft.VisualStudio.Code'; const excludeFlags = '37888'; //Value to exclude un-published, locked or hidden extensions +interface VSCodePublishedExtension extends PublishedExtension { + publisher: { displayName: string; publisherName: string }; +} + export async function search(searchText: string, json: boolean = false): Promise { const api = getPublicGalleryAPI(); - const results = await api.extensionQuery({ + const results = (await api.extensionQuery({ pageSize, criteria: [ { filterType: ExtensionQueryFilterType.SearchText, value: searchText }, @@ -16,7 +24,7 @@ export async function search(searchText: string, json: boolean = false): Promise { filterType: ExtensionQueryFilterType.ExcludeWithFlags, value: excludeFlags }, ], flags: [ExtensionQueryFlags.ExcludeNonValidated, ExtensionQueryFlags.IncludeLatestVersionOnly], - }); + })) as VSCodePublishedExtension[]; if (json) { console.log(JSON.stringify(results, undefined, '\t')); diff --git a/src/show.ts b/src/show.ts index fd681fbc..c58acf75 100644 --- a/src/show.ts +++ b/src/show.ts @@ -11,6 +11,10 @@ export interface ExtensionStatiticsMap { ratingcount: number; } +interface VSCodePublishedExtension extends PublishedExtension { + publisher: { displayName: string; publisherName: string }; +} + export function show(extensionId: string, json: boolean = false): Promise { const flags = [ ExtensionQueryFlags.IncludeCategoryAndTags, @@ -27,7 +31,7 @@ export function show(extensionId: string, json: boolean = false): Promise { if (extension === undefined) { log.error(`Extension "${extensionId}" not found.`); } else { - showOverview(extension); + showOverview(extension as VSCodePublishedExtension); } } }); @@ -44,16 +48,16 @@ function showOverview({ statistics = [], publishedDate, lastUpdated, -}: PublishedExtension) { +}: VSCodePublishedExtension) { const [{ version = 'unknown' } = {}] = versions; // Create formatted table list of versions const versionList = ( - versions.slice(0, limitVersions).map(({ version, lastUpdated }) => [version, formatDate(lastUpdated)]) + 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 }), + (map, { statisticName, value }) => ({ ...map, [statisticName!]: value }), {} ); @@ -80,9 +84,9 @@ function showOverview({ ...tableView([ ['Unique identifier:', `${publisherName}.${extensionName}`], ['Version:', version], - ['Last updated:', formatDateTime(lastUpdated)], + ['Last updated:', formatDateTime(lastUpdated!)], ['Publisher:', publisherDisplayName], - ['Published at:', formatDate(publishedDate)], + ['Published at:', formatDate(publishedDate!)], ]).map(indentRow), '', 'Statistics:', diff --git a/src/store.ts b/src/store.ts index 880b740f..8853f0d6 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { home } from 'osenv'; +import { homedir } from 'os'; import { read, getGalleryAPI, getSecurityRolesAPI, log } from './util'; import { validatePublisher } from './validation'; import { readManifest } from './package'; @@ -18,13 +18,13 @@ export interface IStore extends Iterable { } export class FileStore implements IStore { - private static readonly DefaultPath = path.join(home(), '.vsce'); + private static readonly DefaultPath = path.join(homedir(), '.vsce'); static async open(path: string = FileStore.DefaultPath): Promise { try { const rawStore = await fs.promises.readFile(path, 'utf8'); return new FileStore(path, JSON.parse(rawStore).publishers); - } catch (err) { + } catch (err: any) { if (err.code === 'ENOENT') { return new FileStore(path, []); } else if (/SyntaxError/.test(err)) { diff --git a/src/test/package.test.ts b/src/test/package.test.ts index b192c9d3..e40398da 100644 --- a/src/test/package.test.ts +++ b/src/test/package.test.ts @@ -12,6 +12,7 @@ import { IPackageOptions, ManifestProcessor, versionBump, + VSIX, } from '../package'; import { Manifest } from '../manifest'; import * as path from 'path'; @@ -33,7 +34,7 @@ async function throws(fn: () => Promise): Promise { try { await fn(); - } catch (err) { + } catch (err: any) { didThrow = true; } @@ -42,14 +43,14 @@ async function throws(fn: () => Promise): Promise { } } -const fixture = name => path.join(path.dirname(path.dirname(__dirname)), 'src', 'test', 'fixtures', name); +const fixture = (name: string) => path.join(path.dirname(path.dirname(__dirname)), 'src', 'test', 'fixtures', name); 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 tags = flatten(processors.map(p => p.tags)).join(','); - const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags }); + const vsix = processors.reduce((r, p) => ({ ...r, ...p.vsix }), { assets, tags } as VSIX); return toVsixManifest(vsix); }); @@ -62,15 +63,15 @@ async function toXMLManifest(manifest: Manifest, files: IFile[] = []): Promise p.$.Id === name); - assert.equal(property.length, 1, `Property '${name}' should exist`); + assert.strictEqual(property.length, 1, `Property '${name}' should exist`); const enableMarketplaceQnA = property[0].$.Value; - assert.equal(enableMarketplaceQnA, value, `Property '${name}' should have value '${value}'`); + assert.strictEqual(enableMarketplaceQnA, value, `Property '${name}' should have value '${value}'`); } function assertMissingProperty(manifest: XMLManifest, name: string): void { const property = manifest.PackageManifest.Metadata[0].Properties[0].Property.filter(p => p.$.Id === name); - assert.equal(property.length, 0, `Property '${name}' should not exist`); + assert.strictEqual(property.length, 0, `Property '${name}' should not exist`); } function createManifest(extra: Partial = {}): Manifest { @@ -93,7 +94,7 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd })) .then(files => { - assert.equal(files.length, 3); + assert.strictEqual(files.length, 3); }); }); @@ -111,7 +112,7 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd })) .then(files => { - assert.equal(files.length, 3); + assert.strictEqual(files.length, 3); }); }); @@ -145,7 +146,7 @@ describe('collect', function () { // extension/node_modules/real_sub/package.json // extension/node_modules/real/node_modules/real_sub/dependency.js // extension/node_modules/real/node_modules/real_sub/package.json - assert.equal(files.length, 11); + assert.strictEqual(files.length, 11); assert.ok(files.some(f => /real\/dependency\.js/.test(f.path))); assert.ok(!files.some(f => /fake\/dependency\.js/.test(f.path))); }); @@ -157,7 +158,7 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd })) .then(files => { - assert.equal(files.filter(f => /\.vsixmanifest$/.test(f.path)).length, 1); + assert.strictEqual(files.filter(f => /\.vsixmanifest$/.test(f.path)).length, 1); }); }); @@ -167,14 +168,14 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd, useYarn: true, dependencyEntryPoints: ['isexe'] })) .then(files => { - let seenWhich: boolean; - let seenIsexe: boolean; - files.forEach(file => { + let seenWhich: boolean = false; + let seenIsexe: boolean = false; + for (const file of files) { seenWhich = file.path.indexOf('/node_modules/which/') >= 0; seenIsexe = file.path.indexOf('/node_modules/isexe/') >= 0; - }); - assert.equal(seenWhich, false); - assert.equal(seenIsexe, true); + } + assert.strictEqual(seenWhich, false); + assert.strictEqual(seenIsexe, true); }); }); @@ -184,14 +185,14 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd, dependencyEntryPoints: ['isexe'] })) .then(files => { - let seenWhich: boolean; - let seenIsexe: boolean; - files.forEach(file => { + let seenWhich: boolean = false; + let seenIsexe: boolean = false; + for (const file of files) { seenWhich = file.path.indexOf('/node_modules/which/') >= 0; seenIsexe = file.path.indexOf('/node_modules/isexe/') >= 0; - }); - assert.equal(seenWhich, false); - assert.equal(seenIsexe, true); + } + assert.strictEqual(seenWhich, false); + assert.strictEqual(seenIsexe, true); }); }); @@ -201,14 +202,14 @@ describe('collect', function () { return readManifest(cwd) .then(manifest => collect(manifest, { cwd, useYarn: true })) .then(files => { - let seenWhich: boolean; - let seenIsexe: boolean; - files.forEach(file => { + let seenWhich: boolean = false; + let seenIsexe: boolean = false; + for (const file of files) { seenWhich = file.path.indexOf('/node_modules/which/') >= 0; seenIsexe = file.path.indexOf('/node_modules/isexe/') >= 0; - }); - assert.equal(seenWhich, true); - assert.equal(seenIsexe, true); + } + assert.strictEqual(seenWhich, true); + assert.strictEqual(seenIsexe, true); }); }); @@ -242,9 +243,9 @@ describe('readManifest', () => { 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']); + assert.strictEqual(manifest.name, raw.name); + assert.strictEqual(manifest.description, translations['extension.description']); + assert.strictEqual(manifest.contributes.debuggers[0].label, translations['node.label']); }); }); @@ -254,7 +255,7 @@ describe('readManifest', () => { const translations = require(path.join(cwd, 'package.nls.json')); return readManifest(cwd, false).then((manifest: any) => { - assert.equal(manifest.name, raw.name); + assert.strictEqual(manifest.name, raw.name); assert.notEqual(manifest.description, translations['extension.description']); assert.notEqual(manifest.contributes.debuggers[0].label, translations['node.label']); }); @@ -263,29 +264,29 @@ describe('readManifest', () => { describe('validateManifest', () => { it('should catch missing fields', () => { - assert(validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: { vscode: '0.10.1' } })); + assert.ok(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' } }); + validateManifest({ publisher: undefined!, 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' } }); + 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' } }); + 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 }); + 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 } }); + validateManifest({ publisher: 'demo', name: 'demo', version: '1.0.0', engines: { vscode: null } as any }); }); }); it('should prevent SVG icons', () => { - assert(validateManifest(createManifest({ icon: 'icon.png' }))); + assert.ok(validateManifest(createManifest({ icon: 'icon.png' }))); assert.throws(() => { validateManifest(createManifest({ icon: 'icon.svg' })); }); @@ -312,7 +313,7 @@ describe('validateManifest', () => { }); it('should allow non SVG badges', () => { - assert( + assert.ok( validateManifest( createManifest({ badges: [{ url: 'https://host/badge.png', href: 'http://badgeurl', description: 'this is a badge' }], @@ -322,7 +323,7 @@ describe('validateManifest', () => { }); it('should allow SVG badges from trusted sources', () => { - assert( + assert.ok( validateManifest( createManifest({ badges: [{ url: 'https://gemnasium.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], @@ -333,7 +334,7 @@ describe('validateManifest', () => { it('should prevent SVG badges from non trusted sources', () => { assert.throws(() => { - assert( + assert.ok( validateManifest( createManifest({ badges: [{ url: 'https://github.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], @@ -342,7 +343,7 @@ describe('validateManifest', () => { ); }); assert.throws(() => { - assert( + assert.ok( validateManifest( createManifest({ badges: [ @@ -400,29 +401,32 @@ describe('toVsixManifest', () => { assert.ok(result); assert.ok(result.PackageManifest); 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( + assert.strictEqual(result.PackageManifest.$.Version, '2.0.0'); + assert.strictEqual(result.PackageManifest.$.xmlns, 'http://schemas.microsoft.com/developer/vsx-schema/2011'); + assert.strictEqual( 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'); - assert.equal(result.PackageManifest.Metadata[0].DisplayName[0], 'test'); - assert.equal(result.PackageManifest.Metadata[0].Identity[0].$.Id, 'test'); - assert.equal(result.PackageManifest.Metadata[0].Identity[0].$.Version, '0.0.1'); - assert.equal(result.PackageManifest.Metadata[0].Identity[0].$.Publisher, 'mocha'); + assert.strictEqual(result.PackageManifest.Metadata.length, 1); + assert.strictEqual(result.PackageManifest.Metadata[0].Description[0]._, 'test extension'); + assert.strictEqual(result.PackageManifest.Metadata[0].DisplayName[0], 'test'); + assert.strictEqual(result.PackageManifest.Metadata[0].Identity[0].$.Id, 'test'); + assert.strictEqual(result.PackageManifest.Metadata[0].Identity[0].$.Version, '0.0.1'); + assert.strictEqual(result.PackageManifest.Metadata[0].Identity[0].$.Publisher, 'mocha'); assert.deepEqual(result.PackageManifest.Metadata[0].Tags, ['__web_extension']); assert.deepEqual(result.PackageManifest.Metadata[0].GalleryFlags, ['Public']); - assert.equal(result.PackageManifest.Installation.length, 1); - assert.equal(result.PackageManifest.Installation[0].InstallationTarget.length, 1); - assert.equal(result.PackageManifest.Installation[0].InstallationTarget[0].$.Id, 'Microsoft.VisualStudio.Code'); + assert.strictEqual(result.PackageManifest.Installation.length, 1); + assert.strictEqual(result.PackageManifest.Installation[0].InstallationTarget.length, 1); + assert.strictEqual( + result.PackageManifest.Installation[0].InstallationTarget[0].$.Id, + 'Microsoft.VisualStudio.Code' + ); assert.deepEqual(result.PackageManifest.Dependencies, ['']); - assert.equal(result.PackageManifest.Assets.length, 1); - assert.equal(result.PackageManifest.Assets[0].Asset.length, 1); - assert.equal(result.PackageManifest.Assets[0].Asset[0].$.Type, 'Microsoft.VisualStudio.Code.Manifest'); - assert.equal(result.PackageManifest.Assets[0].Asset[0].$.Path, 'extension/package.json'); + assert.strictEqual(result.PackageManifest.Assets.length, 1); + assert.strictEqual(result.PackageManifest.Assets[0].Asset.length, 1); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[0].$.Type, 'Microsoft.VisualStudio.Code.Manifest'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[0].$.Path, 'extension/package.json'); }); }); @@ -466,12 +470,12 @@ describe('toVsixManifest', () => { return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { - assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal( + assert.strictEqual(result.PackageManifest.Assets[0].Asset.length, 2); + assert.strictEqual( result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.Details' ); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/readme.md'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/readme.md'); }); }); @@ -489,12 +493,12 @@ describe('toVsixManifest', () => { return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { - assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal( + assert.strictEqual(result.PackageManifest.Assets[0].Asset.length, 2); + assert.strictEqual( result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.Changelog' ); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/changelog.md'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/changelog.md'); }); }); @@ -510,8 +514,8 @@ describe('toVsixManifest', () => { return _toVsixManifest(manifest, []) .then(xml => parseXmlManifest(xml)) .then(result => { - assert.equal(result.PackageManifest.Metadata[0].Identity[0].$.Id, 'test'); - assert.equal(result.PackageManifest.Metadata[0].DisplayName[0], 'Test Extension'); + assert.strictEqual(result.PackageManifest.Metadata[0].Identity[0].$.Id, 'test'); + assert.strictEqual(result.PackageManifest.Metadata[0].DisplayName[0], 'Test Extension'); }); }); @@ -530,12 +534,12 @@ describe('toVsixManifest', () => { return _toVsixManifest(manifest, files) .then(xml => parseXmlManifest(xml)) .then(result => { - assert.equal(result.PackageManifest.Assets[0].Asset.length, 2); - assert.equal( + assert.strictEqual(result.PackageManifest.Assets[0].Asset.length, 2); + assert.strictEqual( result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.License' ); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/thelicense.md'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/thelicense.md'); }); }); @@ -555,8 +559,8 @@ describe('toVsixManifest', () => { .then(xml => parseXmlManifest(xml)) .then(result => { assert.ok(result.PackageManifest.Metadata[0].License); - assert.equal(result.PackageManifest.Metadata[0].License.length, 1); - assert.equal(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); + assert.strictEqual(result.PackageManifest.Metadata[0].License.length, 1); + assert.strictEqual(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); }); }); @@ -575,14 +579,14 @@ describe('toVsixManifest', () => { .then(xml => parseXmlManifest(xml)) .then(result => { assert.ok(result.PackageManifest.Metadata[0].License); - 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( + assert.strictEqual(result.PackageManifest.Metadata[0].License.length, 1); + assert.strictEqual(result.PackageManifest.Metadata[0].License[0], 'extension/LICENSE.md'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset.length, 2); + assert.strictEqual( result.PackageManifest.Assets[0].Asset[1].$.Type, 'Microsoft.VisualStudio.Services.Content.License' ); - assert.equal(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/LICENSE.md'); + assert.strictEqual(result.PackageManifest.Assets[0].Asset[1].$.Path, 'extension/LICENSE.md'); }); }); @@ -606,9 +610,9 @@ describe('toVsixManifest', () => { .then(xml => parseXmlManifest(xml)) .then(result => { assert.ok(result.PackageManifest.Metadata[0].Icon); - assert.equal(result.PackageManifest.Metadata[0].Icon.length, 1); - assert.equal(result.PackageManifest.Metadata[0].Icon[0], 'extension/fake.png'); - assert.equal(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); + assert.strictEqual(result.PackageManifest.Metadata[0].Icon.length, 1); + assert.strictEqual(result.PackageManifest.Metadata[0].Icon[0], 'extension/fake.png'); + assert.strictEqual(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); }); }); @@ -655,9 +659,9 @@ describe('toVsixManifest', () => { .then(xml => parseXmlManifest(xml)) .then(result => { assert.ok(result.PackageManifest.Metadata[0].Icon); - assert.equal(result.PackageManifest.Metadata[0].Icon.length, 1); - assert.equal(result.PackageManifest.Metadata[0].Icon[0], 'extension/fake.png'); - assert.equal(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); + assert.strictEqual(result.PackageManifest.Metadata[0].Icon.length, 1); + assert.strictEqual(result.PackageManifest.Metadata[0].Icon[0], 'extension/fake.png'); + assert.strictEqual(result.PackageManifest.Metadata[0].License[0], 'extension/thelicense.md'); }); }); @@ -881,7 +885,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'theme')); + assert.ok(tags.some(tag => tag === 'theme')); }); }); @@ -916,7 +920,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'color-theme')); + assert.ok(tags.some(tag => tag === 'color-theme')); }); }); @@ -935,7 +939,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'theme')); + assert.ok(tags.some(tag => tag === 'theme')); }); }); @@ -954,7 +958,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'icon-theme')); + assert.ok(tags.some(tag => tag === 'icon-theme')); }); }); @@ -973,7 +977,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'product-icon-theme')); + assert.ok(tags.some(tag => tag === 'product-icon-theme')); }); }); @@ -998,7 +1002,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'remote-menu')); + assert.ok(tags.some(tag => tag === 'remote-menu')); }); }); @@ -1077,7 +1081,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'keybindings')); + assert.ok(tags.some(tag => tag === 'keybindings')); }); }); @@ -1104,7 +1108,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'debuggers')); + assert.ok(tags.some(tag => tag === 'debuggers')); }); }); @@ -1128,7 +1132,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'json')); + assert.ok(tags.some(tag => tag === 'json')); }); }); @@ -1145,19 +1149,19 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert( + assert.ok( tags.some(tag => tag === 'c++'), 'detect c++' ); - assert( + assert.ok( tags.some(tag => tag === 'ftp'), 'detect ftp' ); - assert( + assert.ok( tags.some(tag => tag === 'javascript'), 'detect javascript' ); - assert(!tags.includes('java'), "don't detect java"); + assert.ok(!tags.includes('java'), "don't detect java"); }); }); @@ -1182,7 +1186,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'shellscript')); + assert.ok(tags.some(tag => tag === 'shellscript')); }); }); @@ -1206,9 +1210,9 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'go')); - assert(tags.some(tag => tag === 'golang')); - assert(tags.some(tag => tag === 'google-go')); + assert.ok(tags.some(tag => tag === 'go')); + assert.ok(tags.some(tag => tag === 'golang')); + assert.ok(tags.some(tag => tag === 'google-go')); }); }); @@ -1235,11 +1239,11 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === 'lp-de')); - assert(tags.some(tag => tag === '__lp_vscode')); - assert(tags.some(tag => tag === '__lp-de_vscode')); - assert(tags.some(tag => tag === '__lp_vscode.go')); - assert(tags.some(tag => tag === '__lp-de_vscode.go')); + assert.ok(tags.some(tag => tag === 'lp-de')); + assert.ok(tags.some(tag => tag === '__lp_vscode')); + assert.ok(tags.some(tag => tag === '__lp-de_vscode')); + assert.ok(tags.some(tag => tag === '__lp_vscode.go')); + assert.ok(tags.some(tag => tag === '__lp-de_vscode.go')); }); }); @@ -1278,13 +1282,13 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const assets = result.PackageManifest.Assets[0].Asset; - assert( + assert.ok( assets.some( asset => asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.DE' && asset.$.Path === 'extension/de.json' ) ); - assert( + assert.ok( assets.some( asset => asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.PT' && @@ -1294,12 +1298,12 @@ describe('toVsixManifest', () => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; const localizedLangProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.LocalizedLanguages'); - assert.equal(localizedLangProp.length, 1); + assert.strictEqual(localizedLangProp.length, 1); const localizedLangs = localizedLangProp[0].$.Value.split(','); - assert.equal(localizedLangs.length, 2); - assert.equal(localizedLangs[0], 'German'); - assert.equal(localizedLangs[1], 'Português'); + assert.strictEqual(localizedLangs.length, 2); + assert.strictEqual(localizedLangs[0], 'German'); + assert.strictEqual(localizedLangs[1], 'Português'); }); }); @@ -1323,8 +1327,8 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === '__ext_go')); - assert(tags.some(tag => tag === '__ext_golang')); + assert.ok(tags.some(tag => tag === '__ext_go')); + assert.ok(tags.some(tag => tag === '__ext_golang')); }); }); @@ -1348,7 +1352,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - assert(tags.some(tag => tag === '__ext_go')); + assert.ok(tags.some(tag => tag === '__ext_go')); }); }); @@ -1368,13 +1372,13 @@ describe('toVsixManifest', () => { .then(xml => parseXmlManifest(xml)) .then(result => { const badges = result.PackageManifest.Metadata[0].Badges[0].Badge; - assert.equal(badges.length, 2); - assert.equal(badges[0].$.Link, 'http://badgeurl'); - assert.equal(badges[0].$.ImgUri, 'http://badgeurl.png'); - assert.equal(badges[0].$.Description, 'this is a badge'); - assert.equal(badges[1].$.Link, 'http://anotherbadgeurl'); - assert.equal(badges[1].$.ImgUri, 'http://anotherbadgeurl.png'); - assert.equal(badges[1].$.Description, 'this is another badge'); + assert.strictEqual(badges.length, 2); + assert.strictEqual(badges[0].$.Link, 'http://badgeurl'); + assert.strictEqual(badges[0].$.ImgUri, 'http://badgeurl.png'); + assert.strictEqual(badges[0].$.Description, 'this is a badge'); + assert.strictEqual(badges[1].$.Link, 'http://anotherbadgeurl'); + assert.strictEqual(badges[1].$.ImgUri, 'http://anotherbadgeurl.png'); + assert.strictEqual(badges[1].$.Description, 'this is another badge'); }); }); @@ -1404,7 +1408,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const tags = result.PackageManifest.Metadata[0].Tags[0].split(',') as string[]; - tags.forEach(tag => assert(tag, `Found empty tag '${tag}'.`)); + tags.forEach(tag => assert.ok(tag, `Found empty tag '${tag}'.`)); }); }); @@ -1422,10 +1426,10 @@ describe('toVsixManifest', () => { .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; const engineProperties = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.Engine'); - assert.equal(engineProperties.length, 1); + assert.strictEqual(engineProperties.length, 1); const engine = engineProperties[0].$.Value; - assert.equal(engine, '^1.0.0'); + assert.strictEqual(engine, '^1.0.0'); }); }); @@ -1442,7 +1446,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert( + assert.ok( properties.some( p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true' ) @@ -1464,7 +1468,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert( + assert.ok( properties.some( p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'false' ) @@ -1486,7 +1490,7 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - assert( + assert.ok( properties.some( p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'true' ) @@ -1509,12 +1513,12 @@ describe('toVsixManifest', () => { .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; const dependenciesProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionDependencies'); - assert.equal(dependenciesProp.length, 1); + assert.strictEqual(dependenciesProp.length, 1); const dependencies = dependenciesProp[0].$.Value.split(','); - assert.equal(dependencies.length, 2); - assert(dependencies.some(d => d === 'foo.bar')); - assert(dependencies.some(d => d === 'monkey.hello')); + assert.strictEqual(dependencies.length, 2); + assert.ok(dependencies.some(d => d === 'foo.bar')); + assert.ok(dependencies.some(d => d === 'monkey.hello')); }); }); @@ -1534,8 +1538,8 @@ describe('toVsixManifest', () => { try { await _toVsixManifest(manifest, files); - } catch (err) { - assert(/have the same case insensitive path/i.test(err.message)); + } catch (err: any) { + assert.ok(/have the same case insensitive path/i.test(err.message)); return; } @@ -1562,7 +1566,7 @@ describe('toVsixManifest', () => { 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(',')); + assert.strictEqual(extensionKindProps[0].$.Value, ['ui', 'workspace', 'web'].join(',')); }); it('should expose extension kind properties when derived', async () => { @@ -1575,7 +1579,7 @@ describe('toVsixManifest', () => { 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'); + assert.strictEqual(extensionKindProps[0].$.Value, 'workspace'); }); it('should not have target platform by default', async () => { @@ -1605,8 +1609,8 @@ describe('toVsixManifest', () => { try { await _toVsixManifest(manifest, [], { target: 'what' }); - } catch (err) { - return assert(/is not a valid VS Code target/i.test(err.message)); + } catch (err: any) { + return assert.ok(/is not a valid VS Code target/i.test(err.message)); } throw new Error('Should not reach here'); @@ -1617,8 +1621,8 @@ describe('toVsixManifest', () => { try { await _toVsixManifest(manifest, [], { target: 'linux-ia32' }); - } catch (err) { - return assert(/not a valid VS Code target/.test(err.message)); + } catch (err: any) { + return assert.ok(/not a valid VS Code target/.test(err.message)); } throw new Error('Should not reach here'); @@ -1629,8 +1633,8 @@ describe('toVsixManifest', () => { try { await _toVsixManifest(manifest, [], { target: 'linux-ia32' }); - } catch (err) { - return assert(/>=1.61/.test(err.message)); + } catch (err: any) { + return assert.ok(/>=1.61/.test(err.message)); } throw new Error('Should not reach here'); @@ -1725,7 +1729,7 @@ describe('toContentTypes', () => { assert.ok(result); assert.ok(result.Types); assert.ok(result.Types.Default); - assert.equal(result.Types.Default.length, 2); + assert.strictEqual(result.Types.Default.length, 2); assert.ok(result.Types.Default.some(d => d.$.Extension === '.vsixmanifest' && d.$.ContentType === 'text/xml')); assert.ok(result.Types.Default.some(d => d.$.Extension === '.json' && d.$.ContentType === 'application/json')); }); @@ -1771,6 +1775,7 @@ describe('ManifestProcessor', () => { }; const outPackageJson = await processor.onFile(packageJson); + assert.ok(outPackageJson.mode); assert.ok(outPackageJson.mode & 0o200); }); @@ -1836,7 +1841,7 @@ describe('MarkdownProcessor', () => { try { await processor.onFile(readme); - } catch (err) { + } catch (err: any) { didThrow = true; } @@ -1867,7 +1872,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -1894,7 +1899,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -1923,7 +1928,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.branch.main.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -1956,7 +1961,7 @@ describe('MarkdownProcessor', () => { return fs.promises .readFile(path.join(root, 'readme.branch.override.images.expected.md'), 'utf8') .then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -1988,7 +1993,7 @@ describe('MarkdownProcessor', () => { return fs.promises .readFile(path.join(root, 'readme.branch.override.content.expected.md'), 'utf8') .then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2015,7 +2020,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2042,7 +2047,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2069,7 +2074,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2096,7 +2101,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2123,7 +2128,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.default.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2152,7 +2157,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.branch.main.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2185,7 +2190,7 @@ describe('MarkdownProcessor', () => { return fs.promises .readFile(path.join(root, 'readme.gitlab.branch.override.images.expected.md'), 'utf8') .then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2217,7 +2222,7 @@ describe('MarkdownProcessor', () => { return fs.promises .readFile(path.join(root, 'readme.gitlab.branch.override.content.expected.md'), 'utf8') .then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2248,7 +2253,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.images.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2275,7 +2280,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.github.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2302,7 +2307,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.github.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2329,7 +2334,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.github.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2356,7 +2361,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.expected.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2383,7 +2388,7 @@ describe('MarkdownProcessor', () => { .then(file => read(file)) .then(actual => { return fs.promises.readFile(path.join(root, 'readme.gitlab.md'), 'utf8').then(expected => { - assert.equal(actual, expected); + assert.strictEqual(actual, expected); }); }); }); @@ -2446,7 +2451,7 @@ describe('MarkdownProcessor', () => { const readme = { path: 'extension/readme.md', contents }; const file = await processor.onFile(readme); - assert(file); + assert.ok(file); }); it('should allow SVG from GitHub actions in image tag (old url format)', async () => { @@ -2462,7 +2467,7 @@ describe('MarkdownProcessor', () => { const readme = { path: 'extension/readme.md', contents }; const file = await processor.onFile(readme); - assert(file); + assert.ok(file); }); it('should allow SVG from GitHub actions in image tag', async () => { @@ -2478,7 +2483,7 @@ describe('MarkdownProcessor', () => { const readme = { path: 'extension/readme.md', contents }; const file = await processor.onFile(readme); - assert(file); + assert.ok(file); }); it('should prevent SVG from a GitHub repo in image tag', async () => { @@ -2524,7 +2529,7 @@ describe('MarkdownProcessor', () => { const readme = { path: 'extension/readme.md', contents }; const file = await processor.onFile(readme); - assert(file); + assert.ok(file); }); it('should prevent SVG tags', async () => { @@ -2576,7 +2581,7 @@ describe('MarkdownProcessor', () => { describe('version', () => { let dir: tmp.DirResult; const fixtureFolder = fixture('vsixmanifest'); - let cwd; + let cwd: string; const git = (args: string[]) => spawnSync('git', args, { cwd, encoding: 'utf-8' }); diff --git a/src/test/validation.test.ts b/src/test/validation.test.ts index f7d30419..4af57332 100644 --- a/src/test/validation.test.ts +++ b/src/test/validation.test.ts @@ -9,8 +9,8 @@ import { describe('validatePublisher', () => { it('should throw with empty', () => { - assert.throws(() => validatePublisher(null)); - assert.throws(() => validatePublisher(void 0)); + assert.throws(() => validatePublisher(null!)); + assert.throws(() => validatePublisher(undefined!)); assert.throws(() => validatePublisher('')); }); @@ -32,8 +32,8 @@ describe('validatePublisher', () => { describe('validateExtensionName', () => { it('should throw with empty', () => { - assert.throws(() => validateExtensionName(null)); - assert.throws(() => validateExtensionName(void 0)); + assert.throws(() => validateExtensionName(null!)); + assert.throws(() => validateExtensionName(undefined!)); assert.throws(() => validateExtensionName('')); }); @@ -55,8 +55,8 @@ describe('validateExtensionName', () => { describe('validateVersion', () => { it('should throw with empty', () => { - assert.throws(() => validateVersion(null)); - assert.throws(() => validateVersion(void 0)); + assert.throws(() => validateVersion(null!)); + assert.throws(() => validateVersion(undefined!)); assert.throws(() => validateVersion('')); }); @@ -77,8 +77,8 @@ describe('validateVersion', () => { describe('validateEngineCompatibility', () => { it('should throw with empty', () => { - assert.throws(() => validateEngineCompatibility(null)); - assert.throws(() => validateEngineCompatibility(void 0)); + assert.throws(() => validateEngineCompatibility(null!)); + assert.throws(() => validateEngineCompatibility(undefined!)); assert.throws(() => validateEngineCompatibility('')); }); diff --git a/src/typings/parse-semver.d.ts b/src/typings/parse-semver.d.ts new file mode 100644 index 00000000..648b8b70 --- /dev/null +++ b/src/typings/parse-semver.d.ts @@ -0,0 +1,9 @@ +declare module 'parse-semver' { + interface Result { + readonly name: string; + readonly version: string; + } + module parseSemver {} + function parseSemver(input: string): Result; + export = parseSemver; +} diff --git a/src/util.ts b/src/util.ts index 7e12af29..deae2e10 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,5 @@ import { promisify } from 'util'; -import * as _read from 'read'; +import _read from 'read'; import { WebApi, getBasicHandler } from 'azure-devops-node-api/WebApi'; import { IGalleryApi, GalleryApi } from 'azure-devops-node-api/GalleryApi'; import chalk from 'chalk'; @@ -61,7 +61,11 @@ export function chain(initial: T, processors: P[], process: (a: T, b: P) = } export function flatten(arr: T[][]): T[] { - return [].concat.apply([], arr) as T[]; + return ([] as T[]).concat.apply([], arr) as T[]; +} + +export function nonnull(arg: T | null | undefined): arg is T { + return !!arg; } const CancelledError = 'Cancelled'; diff --git a/src/validation.ts b/src/validation.ts index c16fdf83..98244019 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -1,5 +1,5 @@ import * as semver from 'semver'; -import * as parseSemver from 'parse-semver'; +import parseSemver from 'parse-semver'; const nameRegex = /^[a-z0-9][a-z0-9\-]*$/i; diff --git a/src/viewutils.ts b/src/viewutils.ts index 539dad3c..59164027 100644 --- a/src/viewutils.ts +++ b/src/viewutils.ts @@ -3,8 +3,8 @@ export type ViewTable = ViewTableRow[]; const fixedLocale = 'en-us'; const format = { - date: { month: 'long', day: 'numeric', year: 'numeric' }, - time: { hour: 'numeric', minute: 'numeric', second: 'numeric' }, + date: { month: 'long', day: 'numeric', year: 'numeric' } as Intl.DateTimeFormatOptions, + time: { hour: 'numeric', minute: 'numeric', second: 'numeric' } as Intl.DateTimeFormatOptions, }; const columns = process.stdout.columns ? process.stdout.columns : 80; @@ -17,13 +17,13 @@ export const icons = useFallbackIcons ? { download: '\u{2193}', star: '\u{2665}', emptyStar: '\u{2022}' } : { download: '\u{2913}', star: '\u{2605}', emptyStar: '\u{2606}' }; -export function formatDate(date) { +export function formatDate(date: Date) { return date.toLocaleString(fixedLocale, format.date); } -export function formatTime(date) { +export function formatTime(date: Date) { return date.toLocaleString(fixedLocale, format.time); } -export function formatDateTime(date) { +export function formatDateTime(date: Date) { return date.toLocaleString(fixedLocale, { ...format.date, ...format.time }); } @@ -41,7 +41,7 @@ export function ratingStars(rating: number, total = 5): string { } export function tableView(table: ViewTable, spacing: number = 2): string[] { - const maxLen = {}; + const maxLen: Record = {}; 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('') diff --git a/src/xml.ts b/src/xml.ts index 0af4a63a..fb481e88 100644 --- a/src/xml.ts +++ b/src/xml.ts @@ -7,7 +7,7 @@ function createXMLParser(): (raw: string) => Promise { export type XMLManifest = { PackageManifest: { - $: { Version: string; xmlns: string }; + $: { Version: string; xmlns: string; 'xmlns:d': string }; Metadata: { Description: { _: string }[]; DisplayName: string[]; @@ -28,7 +28,7 @@ export type XMLManifest = { export type ContentTypes = { Types: { - Default: { $: { Extension: string; ContentType } }[]; + Default: { $: { Extension: string; ContentType: string } }[]; }; }; diff --git a/src/zip.ts b/src/zip.ts index 28032e36..f3874834 100644 --- a/src/zip.ts +++ b/src/zip.ts @@ -1,12 +1,11 @@ -import { Entry, open, Options, ZipFile } from 'yauzl'; +import { Entry, open, ZipFile } from 'yauzl'; import { Manifest } from './manifest'; import { parseXmlManifest, XMLManifest } from './xml'; -import { promisify } from 'util'; import { Readable } from 'stream'; async function bufferStream(stream: Readable): Promise { return await new Promise((c, e) => { - const buffers = []; + const buffers: Buffer[] = []; stream.on('data', buffer => buffers.push(buffer)); stream.once('error', e); stream.once('end', () => c(Buffer.concat(buffers))); @@ -14,7 +13,9 @@ async function bufferStream(stream: Readable): Promise { } export async function readZip(packagePath: string, filter: (name: string) => boolean): Promise> { - const zipfile = await promisify(open)(packagePath, { lazyEntries: true }); + const zipfile = await new Promise((c, e) => + open(packagePath, { lazyEntries: true }, (err, zipfile) => (err ? e(err) : c(zipfile!))) + ); return await new Promise((c, e) => { const result = new Map(); @@ -32,7 +33,7 @@ export async function readZip(packagePath: string, filter: (name: string) => boo return e(err); } - bufferStream(stream).then(buffer => { + bufferStream(stream!).then(buffer => { result.set(name, buffer); zipfile.readEntry(); }); diff --git a/tsconfig.json b/tsconfig.json index fb3b4c91..c349550e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,21 @@ { "compilerOptions": { - "target": "es2015", + "target": "es2020", "module": "commonjs", - "declaration": true, + "lib": ["es2020"], + "esModuleInterop": true, "outDir": "out", + "allowJs": true, + "strict": true, + "forceConsistentCasingInFileNames": true, "noUnusedLocals": true, "noUnusedParameters": true, + "noImplicitReturns": true, + "declaration": true, "sourceMap": true }, - "include": ["src"] + "include": ["src"], + "ts-node": { + "files": true + } }