diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f8b9a677 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +# https://editorconfig.org/ + +root = true + +[*] +end_of_line = lf +insert_final_newline = true diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..fda0bd99 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "arrowParens": "avoid", + "endOfLine": "lf", + "printWidth": 120, + "quoteProps": "as-needed", + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "es5", + "useTabs": true +} diff --git a/src/main.ts b/src/main.ts index e94b18db..fc058eca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -82,7 +82,10 @@ module.exports = function (argv: string[]): void { program .command('package [version]') .description('Packages an extension') - .option('-o, --out ', 'Output .vsix extension file to location (defaults to -.vsix)') + .option( + '-o, --out ', + 'Output .vsix extension file to location (defaults to -.vsix)' + ) .option('-t, --target ', `Target architecture. Valid targets: ${ValidTargets}`) .option('-m, --message ', 'Commit message used when calling `npm version`.') .option( diff --git a/src/npm.ts b/src/npm.ts index c355cbef..5487bb17 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -10,6 +10,14 @@ const exists = (file: string) => _ => false ); +export enum VersionedPackageManager { + Npm, + Pnpm, + YarnBerry, + YarnClassic, + None, +} + interface IOptions { cwd?: string; stdio?: any; @@ -32,17 +40,21 @@ function exec( return new Promise((c, e) => { let disposeCancellationListener: Function | null = null; - const child = cp.exec(command, { ...options, encoding: 'utf8' } as any, (err, stdout: string, stderr: string) => { - if (disposeCancellationListener) { - disposeCancellationListener(); - disposeCancellationListener = null; - } + const child = cp.exec( + command, + { ...options, encoding: 'utf8' } as any, + (err, stdout: string, stderr: string) => { + if (disposeCancellationListener) { + disposeCancellationListener(); + disposeCancellationListener = null; + } - if (err) { - return e(err); + if (err) { + return e(err); + } + c({ stdout, stderr }); } - c({ stdout, stderr }); - }); + ); if (cancellationToken) { disposeCancellationListener = cancellationToken.subscribe((err: any) => { @@ -154,7 +166,10 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st return reached.values; } -async function getYarnProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise { +async function getYarnBerryProductionDependencies( + cwd: string, + packagedDependencies?: string[] +): Promise { const raw = await new Promise((c, e) => cp.exec( 'yarn list --prod --json', @@ -182,10 +197,48 @@ async function getYarnProductionDependencies(cwd: string, packagedDependencies?: return result; } -async function getYarnDependencies(cwd: string, packagedDependencies?: string[]): Promise { +async function getYarnClassicProductionDependencies( + cwd: string, + packagedDependencies?: string[] +): Promise { + const raw = await new Promise((c, e) => + cp.exec( + 'yarn list --prod --json', + { cwd, encoding: 'utf8', env: { ...process.env }, maxBuffer: 5000 * 1024 }, + (err, stdout) => (err ? e(err) : c(stdout)) + ) + ); + const match = /^{"type":"tree".*$/m.exec(raw); + + if (!match || match.length !== 1) { + throw new Error('Could not parse result of `yarn list --json`'); + } + + const usingPackagedDependencies = Array.isArray(packagedDependencies); + const trees = JSON.parse(match[0]).data.trees as YarnTreeNode[]; + + let result = trees + .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies)) + .filter(nonnull); + + if (usingPackagedDependencies) { + result = selectYarnDependencies(result, packagedDependencies!); + } + + return result; +} + +async function getYarnDependencies( + cwd: string, + packageManager: VersionedPackageManager.YarnBerry | VersionedPackageManager.YarnClassic, + packagedDependencies?: string[] +): Promise { const result = new Set([cwd]); - const deps = await getYarnProductionDependencies(cwd, packagedDependencies); + const deps = + packageManager === VersionedPackageManager.YarnBerry + ? await getYarnBerryProductionDependencies(cwd, packagedDependencies) + : await getYarnClassicProductionDependencies(cwd, packagedDependencies); const flatten = (dep: YarnDependency) => { result.add(dep.path); dep.children.forEach(flatten); @@ -195,6 +248,29 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[]) return [...result]; } +export async function detectPackageManager(cwd: string): Promise { + const cwdFiles = await fs.promises.readdir(cwd); + + switch (true) { + case cwdFiles.includes('yarn.lock'): + const yarnLockFileSource = await fs.promises.readFile(path.normalize(`${cwd}/yarn.lock`), 'utf-8'); + + // We use the lockfile introduction comment and not `.yarn/` / `.yarnrc.yml` to detect Yarn Classic + // because Yarn v3 also generates those when set to `classic` version + return yarnLockFileSource.includes('yarn lockfile v1') + ? VersionedPackageManager.YarnClassic + : VersionedPackageManager.YarnBerry; + + case cwdFiles.includes('pnpm-lock.yaml'): + return VersionedPackageManager.Pnpm; + + case cwdFiles.includes('package-lock.json'): + return VersionedPackageManager.Npm; + } + + return VersionedPackageManager.None; +} + export async function detectYarn(cwd: string): Promise { for (const name of ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']) { if (await exists(path.join(cwd, name))) { @@ -211,13 +287,13 @@ export async function detectYarn(cwd: string): Promise { export async function getDependencies( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + packageManager: VersionedPackageManager | undefined, packagedDependencies?: string[] ): Promise { - if (dependencies === 'none') { + if (packageManager === VersionedPackageManager.None) { return [cwd]; - } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd)))) { - return await getYarnDependencies(cwd, packagedDependencies); + } else if (packageManager === VersionedPackageManager.YarnBerry) { + return await getYarnDependencies(cwd, packageManager, packagedDependencies); } else { return await getNpmDependencies(cwd); } diff --git a/src/package.ts b/src/package.ts index 1012b8c4..42d86cf4 100644 --- a/src/package.ts +++ b/src/package.ts @@ -20,7 +20,7 @@ import { validateEngineCompatibility, validateVSCodeTypesCompatibility, } from './validation'; -import { detectYarn, getDependencies } from './npm'; +import { detectPackageManager, detectYarn, getDependencies, VersionedPackageManager } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -181,7 +181,7 @@ export interface VSIX { } export class BaseProcessor implements IProcessor { - constructor(protected manifest: Manifest) { } + constructor(protected manifest: Manifest) {} assets: IAsset[] = []; tags: string[] = []; vsix: VSIX = Object.create(null); @@ -275,6 +275,14 @@ function toLanguagePackTags(translations: { id: string }[], languageId: string): .reduce((r, t) => [...r, ...t], []); } +const PackageManagerCommand: Record = { + [VersionedPackageManager.None]: 'npm', + [VersionedPackageManager.Npm]: 'npm', + [VersionedPackageManager.Pnpm]: 'pnpm', + [VersionedPackageManager.YarnBerry]: 'yarn', + [VersionedPackageManager.YarnClassic]: 'yarn', +}; + /* This list is also maintained by the Marketplace team. * Remember to reach out to them when adding new domains. */ @@ -453,18 +461,26 @@ export class ManifestProcessor extends BaseProcessor { } if (target) { - if (engineVersion !== 'latest' && !semver.satisfies(engineVersion, '>=1.61', { includePrerelease: true })) { + if ( + engineVersion !== 'latest' && + !semver.satisfies(engineVersion, '>=1.61', { includePrerelease: true }) + ) { throw new Error( `Platform specific extension is supported by VS Code >=1.61. Current 'engines.vscode' is '${manifest.engines['vscode']}'.` ); } if (!Targets.has(target)) { - throw new Error(`'${target}' is not a valid VS Code target. Valid targets: ${[...Targets].join(', ')}`); + throw new Error( + `'${target}' is not a valid VS Code target. Valid targets: ${[...Targets].join(', ')}` + ); } } if (preRelease) { - if (engineVersion !== 'latest' && !semver.satisfies(engineVersion, '>=1.63', { includePrerelease: true })) { + if ( + engineVersion !== 'latest' && + !semver.satisfies(engineVersion, '>=1.63', { includePrerelease: true }) + ) { throw new Error( `Pre-release versions are supported by VS Code >=1.63. Current 'engines.vscode' is '${manifest.engines['vscode']}'.` ); @@ -500,8 +516,8 @@ export class ManifestProcessor extends BaseProcessor { localizedLanguages: manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations - .map(loc => loc.localizedLanguageName ?? loc.languageName ?? loc.languageId) - .join(',') + .map(loc => loc.localizedLanguageName ?? loc.languageName ?? loc.languageId) + .join(',') : '', preRelease: !!this.options.preRelease, sponsorLink: manifest.sponsor?.url || '', @@ -649,7 +665,9 @@ export class TagsProcessor extends BaseProcessor { const descriptionKeywords = Object.keys(TagsProcessor.Keywords).reduce( (r, k) => r.concat( - new RegExp('\\b(?:' + escapeRegExp(k) + ')(?!\\w)', 'gi').test(description) ? TagsProcessor.Keywords[k] : [] + new RegExp('\\b(?:' + escapeRegExp(k) + ')(?!\\w)', 'gi').test(description) + ? TagsProcessor.Keywords[k] + : [] ), [] ); @@ -723,7 +741,9 @@ export class MarkdownProcessor extends BaseProcessor { let contents = await read(file); if (/This is the README for your extension /.test(contents)) { - throw new Error(`It seems the README.md still contains template text. Make sure to edit the README.md file before you package or publish your extension.`); + throw new Error( + `It seems the README.md still contains template text. Make sure to edit the README.md file before you package or publish your extension.` + ); } if (this.rewriteRelativeLinks) { @@ -802,9 +822,10 @@ export class MarkdownProcessor extends BaseProcessor { // Issue in own repository result = prefix + - `[#${issueNumber}](${this.isGitHub - ? urljoin(this.repositoryUrl, 'issues', issueNumber) - : urljoin(this.repositoryUrl, '-', 'issues', issueNumber) + `[#${issueNumber}](${ + this.isGitHub + ? urljoin(this.repositoryUrl, 'issues', issueNumber) + : urljoin(this.repositoryUrl, '-', 'issues', issueNumber) })`; } @@ -829,7 +850,11 @@ export class MarkdownProcessor extends BaseProcessor { 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.pathname)) { + 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}`); } @@ -873,7 +898,7 @@ export class MarkdownProcessor extends BaseProcessor { const gitHubRegex = /(?github(\.com\/|:))(?(?:[^/]+)\/(?:[^/]+))(\/|$)/; const gitLabRegex = /(?gitlab(\.com\/|:))(?(?:[^/]+)(\/(?:[^/]+))+)(\/|$)/; - const match = ((gitHubRegex.exec(repository) || gitLabRegex.exec(repository)) as unknown) as { + const match = (gitHubRegex.exec(repository) || gitLabRegex.exec(repository)) as unknown as { groups: Record; }; @@ -904,7 +929,13 @@ export class MarkdownProcessor extends BaseProcessor { export class ReadmeProcessor extends MarkdownProcessor { constructor(manifest: Manifest, options: IPackageOptions = {}) { - super(manifest, 'README.md', /^extension\/readme.md$/i, 'Microsoft.VisualStudio.Services.Content.Details', options); + super( + manifest, + 'README.md', + /^extension\/readme.md$/i, + 'Microsoft.VisualStudio.Services.Content.Details', + options + ); } } export class ChangelogProcessor extends MarkdownProcessor { @@ -1061,8 +1092,8 @@ function getExtensionKind(manifest: Manifest): ExtensionKind[] { const result: ExtensionKind[] = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : manifest.extensionKind === 'ui' - ? ['ui', 'workspace'] - : [manifest.extensionKind]; + ? ['ui', 'workspace'] + : [manifest.extensionKind]; // Add web kind if the extension can run as web extension if (deduced.includes('web') && !result.includes('web')) { @@ -1267,7 +1298,9 @@ export function validateManifest(manifest: Manifest): Manifest { } 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}`); + throw new Error( + `Badge SVGs are restricted. Please use other file image formats, such as PNG: ${badge.url}` + ); } }); @@ -1280,7 +1313,9 @@ export function validateManifest(manifest: Manifest): Manifest { }); if (manifest.extensionKind) { - const extensionKinds = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : [manifest.extensionKind]; + const extensionKinds = Array.isArray(manifest.extensionKind) + ? manifest.extensionKind + : [manifest.extensionKind]; for (const kind of extensionKinds) { if (!ValidExtensionKinds.has(kind)) { @@ -1375,17 +1410,18 @@ export async function toVsixManifest(vsix: VSIX): Promise { ${escape(vsix.tags)} ${escape(vsix.categories)} ${escape(vsix.flags)} - ${!vsix.badges - ? '' - : `${vsix.badges - .map( - badge => - `` - ) - .join('\n')}` - } + ${ + !vsix.badges + ? '' + : `${vsix.badges + .map( + badge => + `` + ) + .join('\n')}` + } @@ -1393,56 +1429,75 @@ export async function toVsixManifest(vsix: VSIX): Promise { ${vsix.preRelease ? `` : ''} - ${vsix.sponsorLink - ? `` - : '' - } - ${!vsix.links.repository - ? '' - : ` + ${ + vsix.sponsorLink + ? `` + : '' + } + ${ + !vsix.links.repository + ? '' + : ` - ${vsix.links.github - ? `` - : `` - }` - } - ${vsix.links.bugs - ? `` - : '' - } - ${vsix.links.homepage - ? `` - : '' - } - ${vsix.galleryBanner.color - ? `` - : '' - } - ${vsix.galleryBanner.theme - ? `` - : '' - } + ${ + vsix.links.github + ? `` + : `` + }` + } + ${ + vsix.links.bugs + ? `` + : '' + } + ${ + vsix.links.homepage + ? `` + : '' + } + ${ + vsix.galleryBanner.color + ? `` + : '' + } + ${ + vsix.galleryBanner.theme + ? `` + : '' + } - ${vsix.enableMarketplaceQnA !== undefined - ? `` - : '' - } - ${vsix.customerQnALink !== undefined - ? `` - : '' - } + ${ + vsix.enableMarketplaceQnA !== undefined + ? `` + : '' + } + ${ + vsix.customerQnALink !== undefined + ? `` + : '' + } ${vsix.license ? `${escape(vsix.license)}` : ''} ${vsix.icon ? `${escape(vsix.icon)}` : ''} @@ -1454,8 +1509,8 @@ export async function toVsixManifest(vsix: VSIX): Promise { ${vsix.assets - .map(asset => ``) - .join('\n')} + .map(asset => ``) + .join('\n')} `; } @@ -1524,10 +1579,10 @@ const notIgnored = ['!package.json', '!README.md']; async function collectAllFiles( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + packageManager: VersionedPackageManager | undefined, dependencyEntryPoints?: string[] ): Promise { - const deps = await getDependencies(cwd, dependencies, dependencyEntryPoints); + const deps = await getDependencies(cwd, packageManager, 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, '/')) @@ -1539,11 +1594,11 @@ async function collectAllFiles( function collectFiles( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + packageManager: VersionedPackageManager | undefined, dependencyEntryPoints?: string[], ignoreFile?: string ): Promise { - return collectAllFiles(cwd, dependencies, dependencyEntryPoints).then(files => { + return collectAllFiles(cwd, packageManager, dependencyEntryPoints).then(files => { files = files.filter(f => !/\r$/m.test(f)); return ( @@ -1565,7 +1620,9 @@ function collectFiles( // Add '/**' to possible folder names .then(ignore => [ ...ignore, - ...ignore.filter(i => !/(^|\/)[^/]*\*[^/]*$/.test(i)).map(i => (/\/$/.test(i) ? `${i}**` : `${i}/**`)), + ...ignore + .filter(i => !/(^|\/)[^/]*\*[^/]*$/.test(i)) + .map(i => (/\/$/.test(i) ? `${i}**` : `${i}/**`)), ]) // Combine with default ignore list @@ -1635,16 +1692,16 @@ export function createDefaultProcessors(manifest: Manifest, options: IPackageOpt ]; } -function getDependenciesOption(options: IListFilesOptions): 'npm' | 'yarn' | 'none' | undefined { +function getDependenciesOption(options: IListFilesOptions): VersionedPackageManager | undefined { if (options.dependencies === false) { - return 'none'; + return VersionedPackageManager.None; } switch (options.useYarn) { case true: - return 'yarn'; + return VersionedPackageManager.YarnClassic; case false: - return 'npm'; + return VersionedPackageManager.Npm; default: return undefined; } @@ -1673,9 +1730,13 @@ function writeVsix(files: IFile[], packagePath: string): Promise { 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, { - mode: f.mode, - }) + ? zip.addBuffer( + typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, + f.path, + { + mode: f.mode, + } + ) : zip.addFile(f.localPath, f.path, { mode: f.mode }) ); zip.end(); @@ -1713,12 +1774,18 @@ export async function prepublish(cwd: string, manifest: Manifest, useYarn?: bool useYarn = await detectYarn(cwd); } - console.log(`Executing prepublish script '${useYarn ? 'yarn' : 'npm'} run vscode:prepublish'...`); + const packageManager = await detectPackageManager(cwd); + const packageManagerCommand = PackageManagerCommand[packageManager]; + + console.log(`Executing prepublish script '${packageManagerCommand} run vscode:prepublish'...`); 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}`))); + const child = cp.spawn(packageManagerCommand, ['run', 'vscode:prepublish'], { + cwd, + shell: true, + stdio: 'inherit', + }); + child.on('exit', code => (code === 0 ? c() : e(`${packageManagerCommand} failed with exit code ${code}`))); child.on('error', e); }); } diff --git a/src/publish.ts b/src/publish.ts index b92aacbf..b0c48249 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -135,10 +135,14 @@ async function _publish(packagePath: string, manifest: Manifest, options: IInter validatePublisher(manifest.publisher); 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.noVerify && manifest.enabledApiProposals) { - throw new Error("Extensions using proposed API (enabledApiProposals: [...]) can't be published to the Marketplace"); + throw new Error( + "Extensions using proposed API (enabledApiProposals: [...]) can't be published to the Marketplace" + ); } if (semver.prerelease(manifest.version)) { diff --git a/src/search.ts b/src/search.ts index 252af526..97bf7a17 100644 --- a/src/search.ts +++ b/src/search.ts @@ -67,7 +67,9 @@ function buildResultTableView(results: VSCodePublishedExtension[], stats: boolea publisher.publisherName + '.' + extensionName, publisher.displayName, wordTrim(displayName || '', 25), - stats ? buildExtensionStatisticsText(statistics!) : wordTrim(shortDescription || '', 150).replace(/\n|\r|\t/g, ' '), + stats + ? buildExtensionStatisticsText(statistics!) + : wordTrim(shortDescription || '', 150).replace(/\n|\r|\t/g, ' '), ]); var resultsTableHeaders = stats @@ -80,7 +82,11 @@ function buildResultTableView(results: VSCodePublishedExtension[], stats: boolea } function buildExtensionStatisticsText(statistics: ExtensionStatistic[]): string { - const { install: installs = 0, averagerating = 0, ratingcount = 0 } = statistics?.reduce( + const { + install: installs = 0, + averagerating = 0, + ratingcount = 0, + } = statistics?.reduce( (map, { statisticName, value }) => ({ ...map, [statisticName!]: value }), {} ); diff --git a/src/show.ts b/src/show.ts index d9f5c193..828e2e02 100644 --- a/src/show.ts +++ b/src/show.ts @@ -56,7 +56,11 @@ function showOverview({ versions.slice(0, limitVersions).map(({ version, lastUpdated }) => [version, formatDate(lastUpdated!)]) ); - const { install: installs = 0, averagerating = 0, ratingcount = 0 } = statistics.reduce( + const { + install: installs = 0, + averagerating = 0, + ratingcount = 0, + } = statistics.reduce( (map, { statisticName, value }) => ({ ...map, [statisticName!]: value }), {} ); diff --git a/src/test/package.test.ts b/src/test/package.test.ts index d9553224..ecba377f 100644 --- a/src/test/package.test.ts +++ b/src/test/package.test.ts @@ -270,7 +270,9 @@ describe('readManifest', () => { describe('validateManifest', () => { it('should catch missing fields', () => { - assert.ok(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: 'demo', name: null!, version: '1.0.0', engines: { vscode: '0.10.1' } }); }); @@ -300,12 +302,16 @@ describe('validateManifest', () => { it('should prevent badges from non HTTPS sources', () => { assert.throws(() => { validateManifest( - createManifest({ badges: [{ url: 'relative.png', href: 'http://badgeurl', description: 'this is a badge' }] }) + 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' }] }) + createManifest({ + badges: [{ url: 'relative.svg', href: 'http://badgeurl', description: 'this is a badge' }], + }) ); }); assert.throws(() => { @@ -321,7 +327,9 @@ describe('validateManifest', () => { assert.ok( validateManifest( createManifest({ - badges: [{ url: 'https://host/badge.png', href: 'http://badgeurl', description: 'this is a badge' }], + badges: [ + { url: 'https://host/badge.png', href: 'http://badgeurl', description: 'this is a badge' }, + ], }) ) ); @@ -331,7 +339,13 @@ describe('validateManifest', () => { assert.ok( validateManifest( createManifest({ - badges: [{ url: 'https://gemnasium.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], + badges: [ + { + url: 'https://gemnasium.com/foo.svg', + href: 'http://badgeurl', + description: 'this is a badge', + }, + ], }) ) ); @@ -342,7 +356,13 @@ describe('validateManifest', () => { assert.ok( validateManifest( createManifest({ - badges: [{ url: 'https://github.com/foo.svg', href: 'http://badgeurl', description: 'this is a badge' }], + badges: [ + { + url: 'https://github.com/foo.svg', + href: 'http://badgeurl', + description: 'this is a badge', + }, + ], }) ) ); @@ -441,9 +461,9 @@ describe('validateManifest', () => { languages: [ { id: 'typescript', - } - ] - } + }, + ], + }, }) ); @@ -501,7 +521,10 @@ describe('toVsixManifest', () => { assert.ok(result.PackageManifest); assert.ok(result.PackageManifest.$); 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, + 'http://schemas.microsoft.com/developer/vsx-schema/2011' + ); assert.strictEqual( result.PackageManifest.$['xmlns:d'], 'http://schemas.microsoft.com/developer/vsx-schema-design/2011' @@ -524,7 +547,10 @@ describe('toVsixManifest', () => { assert.deepEqual(result.PackageManifest.Dependencies, ['']); 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].$.Type, + 'Microsoft.VisualStudio.Code.Manifest' + ); assert.strictEqual(result.PackageManifest.Assets[0].Asset[0].$.Path, 'extension/package.json'); }); }); @@ -732,7 +758,9 @@ describe('toVsixManifest', () => { .then(result => { assert.ok( result.PackageManifest.Assets[0].Asset.some( - d => d.$.Type === 'Microsoft.VisualStudio.Services.Icons.Default' && d.$.Path === 'extension/fake.png' + d => + d.$.Type === 'Microsoft.VisualStudio.Services.Icons.Default' && + d.$.Path === 'extension/fake.png' ) ); }); @@ -781,10 +809,14 @@ describe('toVsixManifest', () => { .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') + 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') + properties.some( + p => p.Id === 'Microsoft.VisualStudio.Services.Branding.Theme' && p.Value === 'dark' + ) ); }); }); @@ -1384,7 +1416,8 @@ describe('toVsixManifest', () => { assert.ok( assets.some( asset => - asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.DE' && asset.$.Path === 'extension/de.json' + asset.$.Type === 'Microsoft.VisualStudio.Code.Translation.DE' && + asset.$.Path === 'extension/de.json' ) ); assert.ok( @@ -1396,7 +1429,9 @@ describe('toVsixManifest', () => { ); const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - const localizedLangProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.LocalizedLanguages'); + const localizedLangProp = properties.filter( + p => p.$.Id === 'Microsoft.VisualStudio.Code.LocalizedLanguages' + ); assert.strictEqual(localizedLangProp.length, 1); const localizedLangs = localizedLangProp[0].$.Value.split(','); @@ -1463,7 +1498,11 @@ 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', + }, ], }; @@ -1569,7 +1608,8 @@ describe('toVsixManifest', () => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; assert.ok( properties.some( - p => p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'false' + p => + p.$.Id === 'Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown' && p.$.Value === 'false' ) ); }); @@ -1611,7 +1651,9 @@ describe('toVsixManifest', () => { .then(parseXmlManifest) .then(result => { const properties = result.PackageManifest.Metadata[0].Properties[0].Property; - const dependenciesProp = properties.filter(p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionDependencies'); + const dependenciesProp = properties.filter( + p => p.$.Id === 'Microsoft.VisualStudio.Code.ExtensionDependencies' + ); assert.strictEqual(dependenciesProp.length, 1); const dependencies = dependenciesProp[0].$.Value.split(','); @@ -1914,8 +1956,12 @@ describe('toContentTypes', () => { assert.ok(result.Types); assert.ok(result.Types.Default); 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')); + 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') + ); }); }); @@ -1940,7 +1986,9 @@ describe('toContentTypes', () => { 'there are png' ); assert.ok( - result.Types.Default.some(d => d.$.Extension === '.md' && /^text\/(x-)?markdown$/.test(d.$.ContentType)), + 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 === '')); @@ -2140,9 +2188,11 @@ describe('MarkdownProcessor', () => { .onFile(readme) .then(file => read(file)) .then(actual => { - return fs.promises.readFile(path.join(root, 'readme.branch.main.expected.md'), 'utf8').then(expected => { - assert.strictEqual(actual, expected); - }); + return fs.promises + .readFile(path.join(root, 'readme.branch.main.expected.md'), 'utf8') + .then(expected => { + assert.strictEqual(actual, expected); + }); }); }); @@ -2369,9 +2419,11 @@ describe('MarkdownProcessor', () => { .onFile(readme) .then(file => read(file)) .then(actual => { - return fs.promises.readFile(path.join(root, 'readme.gitlab.branch.main.expected.md'), 'utf8').then(expected => { - assert.strictEqual(actual, expected); - }); + return fs.promises + .readFile(path.join(root, 'readme.gitlab.branch.main.expected.md'), 'utf8') + .then(expected => { + assert.strictEqual(actual, expected); + }); }); }); diff --git a/src/viewutils.ts b/src/viewutils.ts index 59164027..7a46dded 100644 --- a/src/viewutils.ts +++ b/src/viewutils.ts @@ -58,7 +58,9 @@ export function wordWrap(text: string, width: number = columns): string { ([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]; + return / |-|,|\./.test(ch) + ? [`${out}${buffer}${ch}${nl}`, '', newPos] + : [`${out}${nl}`, buffer + ch, newPos]; }, [indent, '', 0] )