Skip to content

Commit

Permalink
vsx: update compatibility check for builtins
Browse files Browse the repository at this point in the history
The changeset updates the logic when getting the latest compatible
version of a builtin extension (individual or pack) by fetching the
version which is defined by api version supported by the framework.

Since the `engines.vscode` property is generally not maintained by
vscode builtin extensions (since they are bundled with vscode at a
specific version) we cannot rely on the `engines.vscode` version to
fetch a compatible extension. Instead, we now fetch a version which
corresponds to the api level the framework defines.

Signed-off-by: vince-fugnitto <[email protected]>
  • Loading branch information
vince-fugnitto committed May 25, 2021
1 parent 38f176a commit dab288c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 11 deletions.
2 changes: 1 addition & 1 deletion packages/vsx-registry/src/browser/vsx-extensions-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export class VSXExtensionsModel {
const searchResult = new Set<string>();
for (const data of result.extensions) {
const id = data.namespace.toLowerCase() + '.' + data.name.toLowerCase();
const extension = this.api.getLatestCompatibleVersion(data.allVersions);
const extension = this.api.getLatestCompatibleVersion(data);
if (!extension) {
continue;
}
Expand Down
10 changes: 10 additions & 0 deletions packages/vsx-registry/src/common/vsx-registry-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,14 @@ describe('VSX Registry API', () => {

});

describe.only('#isVersionLTE', () => {

it('should determine if v1 is less than or equal to v2', () => {
expect(api['isVersionLTE']('1.40.0', '1.50.0')).equal(true, 'should be satisfied since v1 is less than v2');
expect(api['isVersionLTE']('1.50.0', '1.50.0')).equal(true, 'should be satisfied since v1 and v2 are equal');
expect(api['isVersionLTE']('2.0.2', '2.0.1')).equal(false, 'should not be satisfied since v1 is greater than v2');
});

});

});
69 changes: 59 additions & 10 deletions packages/vsx-registry/src/common/vsx-registry-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as bent from 'bent';
import * as semver from 'semver';
import { injectable, inject } from '@theia/core/shared/inversify';
import { VSXExtensionRaw, VSXSearchParam, VSXSearchResult, VSXAllVersions } from './vsx-registry-types';
import { VSXExtensionRaw, VSXSearchParam, VSXSearchResult, VSXAllVersions, VSXBuiltinNamespaces, VSXSearchEntry } from './vsx-registry-types';
import { VSXEnvironment } from './vsx-environment';
import { VSXApiVersionProvider } from './vsx-api-version-provider';

Expand Down Expand Up @@ -131,31 +131,56 @@ export class VSXRegistryAPI {

/**
* Get the latest compatible extension version.
* - a builtin extension is fetched based on the extension version which matches the API.
* - an extension satisfies compatibility if its `engines.vscode` version is supported.
* @param id the extension id.
*
* @returns the data for the latest compatible extension version if available, else `undefined`.
*/
async getLatestCompatibleExtensionVersion(id: string): Promise<VSXExtensionRaw | undefined> {
const extensions = await this.getAllVersions(id);
for (let i = 0; i < extensions.length; i++) {
const extension: VSXExtensionRaw = extensions[i];
if (extension.engines && this.isEngineSupported(extension.engines.vscode)) {
return extension;
if (extensions.length === 0) {
return undefined;
}

const namespace = extensions[0].namespace.toLowerCase();
if (this.isBuiltinNamespace(namespace)) {
const apiVersion = this.apiVersionProvider.getApiVersion();
for (const extension of extensions) {
if (this.isVersionLTE(extension.version, apiVersion)) {
return extension;
}
}
console.log(`Skipping: built-in extension "${id}" at version "${apiVersion}" does not exist.`);
} else {
for (const extension of extensions) {
if (this.isEngineSupported(extension.engines?.vscode)) {
return extension;
}
}
}
}

/**
* Get the latest compatible version of an extension.
* @param versions the `allVersions` property.
* @param entry the extension search entry.
*
* @returns the latest compatible version of an extension if it exists, else `undefined`.
*/
getLatestCompatibleVersion(versions: VSXAllVersions[]): VSXAllVersions | undefined {
for (const version of versions) {
if (this.isEngineSupported(version.engines?.vscode)) {
return version;
getLatestCompatibleVersion(entry: VSXSearchEntry): VSXAllVersions | undefined {
const extensions = entry.allVersions;
if (this.isBuiltinNamespace(entry.namespace)) {
const apiVersion = this.apiVersionProvider.getApiVersion();
for (const extension of extensions) {
if (this.isVersionLTE(extension.version, apiVersion)) {
return extension;
}
}
} else {
for (const extension of extensions) {
if (this.isEngineSupported(extension.engines?.vscode)) {
return extension;
}
}
}
}
Expand All @@ -180,6 +205,30 @@ export class VSXRegistryAPI {
}
}

/**
* Determines if the extension namespace is a builtin maintained by the framework.
* @param namespace the extension namespace to verify.
*/
protected isBuiltinNamespace(namespace: string): boolean {
return namespace === VSXBuiltinNamespaces.VSCODE
|| namespace === VSXBuiltinNamespaces.THEIA;
}

/**
* Determines if the first version is less than or equal the second version.
* - v1 <= v2.
* @param a the first semver version.
* @param b the second semver version.
*/
protected isVersionLTE(a: string, b: string): boolean {
const versionA = semver.clean(a);
const versionB = semver.clean(b);
if (!versionA || !versionB) {
return false;
}
return semver.lte(versionA, versionB);
}

}

interface QueryParam {
Expand Down
15 changes: 15 additions & 0 deletions packages/vsx-registry/src/common/vsx-registry-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,18 @@ export interface VSXExtensionRaw {
readonly qna?: string;
readonly engines?: { [engine: string]: string };
}

/**
* Builtin namespaces maintained by the framework.
*/
export namespace VSXBuiltinNamespaces {
/**
* Namespace for individual vscode builtin extensions.
*/
export const VSCODE = 'vscode';
/**
* Namespace for vscode builtin extension packs.
* - corresponds to: https://github.com/eclipse-theia/vscode-builtin-extensions/blob/af9cfeb2ea23e1668a8340c1c2fb5afd56be07d7/src/create-extension-pack.js#L45
*/
export const THEIA = 'eclipse-theia';
}

0 comments on commit dab288c

Please sign in to comment.