From ecc3d1595f738cd432c18f98d4c44774736b67c4 Mon Sep 17 00:00:00 2001 From: Max Kless Date: Wed, 21 Sep 2022 14:14:33 +0300 Subject: [PATCH] add version selection --- .../src/lib/dependency-versioning.ts | 158 ++++++++++++++++++ .../src/lib/vscode-add-dependency.ts | 88 +--------- 2 files changed, 160 insertions(+), 86 deletions(-) create mode 100644 libs/vscode/add-dependency/src/lib/dependency-versioning.ts diff --git a/libs/vscode/add-dependency/src/lib/dependency-versioning.ts b/libs/vscode/add-dependency/src/lib/dependency-versioning.ts new file mode 100644 index 0000000000..92d23d8ac7 --- /dev/null +++ b/libs/vscode/add-dependency/src/lib/dependency-versioning.ts @@ -0,0 +1,158 @@ +import { xhr } from 'request-light'; +import { gte, rcompare } from 'semver'; +import { QuickPickItem, QuickPickItemKind, window } from 'vscode'; + +type VersionMap = Record; + +export async function resolveDependencyVersioning( + depInput: string +): Promise<{ dep: string; version: string | undefined } | undefined> { + const match = depInput.match(/^(.+)@(.+)/); + if (match) { + const [_, dep, version] = match; + return { dep, version }; + } + let packageInfo: PackageInformationResponse; + try { + packageInfo = await getPackageInfo(depInput); + } catch (e) { + window.showErrorMessage( + `Package ${depInput} couldn't be found. Are you sure it exists?` + ); + return { dep: depInput, version: undefined }; + } + + const versionMap = createVersionMap(packageInfo); + const versionQuickPickOptions = createVersionQuickPickItems(versionMap); + + const version = await promptForVersion(versionQuickPickOptions, versionMap); + + return { dep: depInput, version }; +} + +async function promptForVersion( + versionQuickPickItems: QuickPickItem[], + versionMap: VersionMap +): Promise { + const selection = await new Promise((resolve) => { + const quickPick = window.createQuickPick(); + quickPick.canSelectMany = false; + + quickPick.items = versionQuickPickItems; + + quickPick.onDidChangeValue(() => { + quickPick.items = [ + ...versionQuickPickItems, + { + label: quickPick.value, + description: 'install specific version', + }, + ]; + }); + + quickPick.onDidAccept(() => { + resolve(quickPick.selectedItems[0]?.label); + quickPick.hide(); + }); + + quickPick.show(); + }); + const match = selection?.match(/^(\d+).x/); + if (match) { + const majorToSelect = match[1]; + const version = await window.showQuickPick(versionMap[majorToSelect].all, { + canPickMany: false, + }); + if (!version) { + return promptForVersion(versionQuickPickItems, versionMap); + } + return version; + } + return selection; +} + +/** + * Create a map that tracks the latest version and an array of all versions per major version + */ +function createVersionMap(packageInfo: PackageInformationResponse): VersionMap { + const versionMap: VersionMap = {}; + Object.entries(packageInfo.versions).forEach(([versionNum, versionInfo]) => { + if (versionInfo.deprecated) { + return; + } + const major = versionNum.split('.')[0]; + if (!versionMap[major]) { + versionMap[major] = { latest: versionNum, all: [] }; + } + versionMap[major].all.push(versionNum); + if (gte(versionNum, versionMap[major].latest)) { + versionMap[major].latest = versionNum; + } + }); + return versionMap; +} + +/** + * For each major version, add the following options to the quickpick: + * - the latest version of that major + * - the patch before the latest version of that major + * - the minor before the latest version of that major + * - the option to select a specific version of that major + */ +function createVersionQuickPickItems(versionMap: VersionMap): QuickPickItem[] { + return Object.entries(versionMap) + .sort( + ( + a: [keyof VersionMap, VersionMap[keyof VersionMap]], + b: [keyof VersionMap, VersionMap[keyof VersionMap]] + ) => (parseInt(a[0]) < parseInt(b[0]) ? 1 : -1) + ) + .flatMap(([major, { latest, all }], index) => { + const allSorted = all.sort(rcompare); + const quickPickOptions = []; + quickPickOptions.push({ + label: `Version ${major}.x`, + kind: QuickPickItemKind.Separator, + }); + quickPickOptions.push({ + label: latest, + description: index === 0 ? 'latest' : '', + }); + if (allSorted.length > 1) { + quickPickOptions.push({ label: allSorted[1] }); + } + const minorBefore = allSorted.find( + (v) => + v.split('.')[1] === (parseInt(latest.split('.')[1]) - 1).toString() + ); + if (minorBefore && minorBefore !== allSorted[1]) { + quickPickOptions.push({ label: minorBefore }); + } + if (allSorted.length > 2) { + quickPickOptions.push({ + label: `${major}.x`, + description: 'select specific version', + }); + } + return quickPickOptions; + }); +} + +type PackageInformationResponse = { + versions: Record; +}; +function getPackageInfo(dep: string): Promise { + const headers = { + 'Accept-Encoding': 'gzip, deflate', + Accept: 'application/vnd.npm.install-v1+json', + }; + + // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md + return xhr({ + url: `https://registry.npmjs.org/${dep}`, + headers, + }).then( + (res) => JSON.parse(res.responseText), + (error) => Promise.reject(error) + ); +} diff --git a/libs/vscode/add-dependency/src/lib/vscode-add-dependency.ts b/libs/vscode/add-dependency/src/lib/vscode-add-dependency.ts index 9cd522157d..7b85edd448 100644 --- a/libs/vscode/add-dependency/src/lib/vscode-add-dependency.ts +++ b/libs/vscode/add-dependency/src/lib/vscode-add-dependency.ts @@ -17,6 +17,7 @@ import { ExtensionContext, QuickInput, QuickPickItem, + QuickPickItemKind, ShellExecution, Task, tasks, @@ -24,6 +25,7 @@ import { window, } from 'vscode'; import { gte, major, rcompare } from 'semver'; +import { resolveDependencyVersioning } from './dependency-versioning'; export const ADD_DEPENDENCY_COMMAND = 'nxConsole.addDependency'; export const ADD_DEV_DEPENDENCY_COMMAND = 'nxConsole.addDevDependency'; @@ -114,73 +116,6 @@ async function promptForDependencyInput(): Promise { return dep; } -async function resolveDependencyVersioning( - depInput: string -): Promise<{ dep: string; version: string | undefined } | undefined> { - const match = depInput.match(/^(.+)@(.+)/); - if (match) { - const [_, dep, version] = match; - return { dep, version }; - } - let packageInfo: PackageInformationResponse; - try { - packageInfo = await getPackageInfo(depInput); - } catch (e) { - window.showErrorMessage( - `Package ${depInput} couldn't be found. Are you sure it exists?` - ); - return { dep: depInput, version: undefined }; - } - const versionMap: Record = {}; - Object.entries(packageInfo.versions).forEach(([versionNum, versionInfo]) => { - if (versionInfo.deprecated) { - return; - } - const major = versionNum.split('.')[0]; - if (!versionMap[major]) { - versionMap[major] = { latest: versionNum, all: [] }; - } - versionMap[major].all.push(versionNum); - if (gte(versionNum, versionMap[major].latest)) { - versionMap[major].latest = versionNum; - } - }); - - const version = await new Promise((resolve) => { - const quickPick = window.createQuickPick(); - quickPick.canSelectMany = false; - - const options: QuickPickItem[] = [ - 'latest', - ...Object.values(versionMap) - .map((v) => v.latest) - .sort(rcompare), - ].map((o) => ({ - label: o, - })); - - quickPick.items = options; - - quickPick.onDidChangeValue(() => { - quickPick.items = [ - ...options, - { - label: quickPick.value, - }, - ]; - }); - - quickPick.onDidAccept(() => { - resolve(quickPick.selectedItems[0]?.label); - quickPick.hide(); - }); - - quickPick.show(); - }); - - return { dep: depInput, version }; -} - function showLoadingQuickInput(dependency: string): QuickInput { const quickInput = window.createQuickPick(); quickInput.busy = true; @@ -308,22 +243,3 @@ function getDependencySuggestions(): Promise< } ); } - -type PackageInformationResponse = { - versions: Record; -}; -function getPackageInfo(dep: string): Promise { - const headers = { - 'Accept-Encoding': 'gzip, deflate', - Accept: 'application/vnd.npm.install-v1+json', - }; - - // https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md - return xhr({ - url: `https://registry.npmjs.org/${dep}`, - headers, - }).then( - (res) => JSON.parse(res.responseText), - (error) => Promise.reject(error) - ); -}