Skip to content

Commit

Permalink
Use targetPlatform when installing plugin from open-vsx (#13825)
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew authored Jun 25, 2024
1 parent adf13a0 commit 1a5d8d8
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 53 deletions.
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@
"request": "launch",
"name": "Launch Browser Backend",
"program": "${workspaceFolder}/examples/browser/lib/backend/main.js",
"cwd": "${workspaceFolder}/examples/browser",
"args": [
"--hostname=0.0.0.0",
"--port=3000",
"--no-cluster",
"--app-project-path=${workspaceFolder}/examples/browser",
"--plugins=local-dir:plugins",
"--plugins=local-dir:../../plugins",
"--hosted-plugin-inspect=9339",
"--ovsx-router-config=${workspaceFolder}/examples/ovsx-router-config.json"
],
Expand Down
15 changes: 10 additions & 5 deletions dev-packages/cli/src/download-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

/* eslint-disable @typescript-eslint/no-explicit-any */

import { OVSXApiFilterImpl, OVSXClient } from '@theia/ovsx-client';
import { OVSXApiFilterImpl, OVSXClient, VSXTargetPlatform } from '@theia/ovsx-client';
import * as chalk from 'chalk';
import * as decompress from 'decompress';
import { promises as fs } from 'fs';
Expand Down Expand Up @@ -75,7 +75,7 @@ export default async function downloadPlugins(ovsxClient: OVSXClient, requestSer
} = options;

const rateLimiter = new RateLimiter({ tokensPerInterval: rateLimit, interval: 'second' });
const apiFilter = new OVSXApiFilterImpl(apiVersion);
const apiFilter = new OVSXApiFilterImpl(ovsxClient, apiVersion);

// Collect the list of failures to be appended at the end of the script.
const failures: string[] = [];
Expand Down Expand Up @@ -129,8 +129,11 @@ export default async function downloadPlugins(ovsxClient: OVSXClient, requestSer
await parallelOrSequence(Array.from(ids, id => async () => {
try {
await rateLimiter.removeTokens(1);
const { extensions } = await ovsxClient.query({ extensionId: id, includeAllVersions: true });
const extension = apiFilter.getLatestCompatibleExtension(extensions);
const extension = await apiFilter.findLatestCompatibleExtension({
extensionId: id,
includeAllVersions: true,
targetPlatform
});
const version = extension?.version;
const downloadUrl = extension?.files.download;
if (downloadUrl) {
Expand Down Expand Up @@ -170,8 +173,10 @@ export default async function downloadPlugins(ovsxClient: OVSXClient, requestSer
}
}

const targetPlatform = `${process.platform}-${process.arch}` as VSXTargetPlatform;

const placeholders: Record<string, string> = {
targetPlatform: `${process.platform}-${process.arch}`
targetPlatform
};
function resolveDownloadUrlPlaceholders(url: string): string {
for (const [name, value] of Object.entries(placeholders)) {
Expand Down
2 changes: 1 addition & 1 deletion dev-packages/ovsx-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

export { OVSXApiFilter, OVSXApiFilterImpl } from './ovsx-api-filter';
export { OVSXApiFilter, OVSXApiFilterImpl, OVSXApiFilterProvider } from './ovsx-api-filter';
export { OVSXHttpClient } from './ovsx-http-client';
export { OVSXMockClient } from './ovsx-mock-client';
export { OVSXRouterClient, OVSXRouterConfig, OVSXRouterFilterFactory as FilterFactory } from './ovsx-router-client';
Expand Down
47 changes: 46 additions & 1 deletion dev-packages/ovsx-client/src/ovsx-api-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
// *****************************************************************************

import * as semver from 'semver';
import { VSXAllVersions, VSXBuiltinNamespaces, VSXExtensionRaw, VSXSearchEntry } from './ovsx-types';
import { OVSXClient, VSXAllVersions, VSXBuiltinNamespaces, VSXExtensionRaw, VSXQueryOptions, VSXSearchEntry } from './ovsx-types';

export const OVSXApiFilterProvider = Symbol('OVSXApiFilterProvider');

export type OVSXApiFilterProvider = () => Promise<OVSXApiFilter>;

export const OVSXApiFilter = Symbol('OVSXApiFilter');
/**
* Filter various data types based on a pre-defined supported VS Code API version.
*/
export interface OVSXApiFilter {
supportedApiVersion: string;
findLatestCompatibleExtension(query: VSXQueryOptions): Promise<VSXExtensionRaw | undefined>;
/**
* Get the latest compatible extension version:
* - A builtin extension is fetched based on the extension version which matches the API.
Expand All @@ -38,9 +43,49 @@ export interface OVSXApiFilter {
export class OVSXApiFilterImpl implements OVSXApiFilter {

constructor(
public client: OVSXClient,
public supportedApiVersion: string
) { }

async findLatestCompatibleExtension(query: VSXQueryOptions): Promise<VSXExtensionRaw | undefined> {
const targetPlatform = query.targetPlatform;
if (!targetPlatform) {
return this.queryLatestCompatibleExtension(query);
}
const latestWithTargetPlatform = await this.queryLatestCompatibleExtension(query);
let latestUniversal: VSXExtensionRaw | undefined;
if (targetPlatform !== 'universal' && targetPlatform !== 'web') {
// Additionally query the universal version, as there might be a newer one available
latestUniversal = await this.queryLatestCompatibleExtension({ ...query, targetPlatform: 'universal' });
}
if (latestWithTargetPlatform && latestUniversal) {
// Prefer the version with the target platform if it's greater or equal to the universal version
return this.versionGreaterThanOrEqualTo(latestWithTargetPlatform.version, latestUniversal.version) ? latestWithTargetPlatform : latestUniversal;
}
return latestWithTargetPlatform ?? latestUniversal;
}

protected async queryLatestCompatibleExtension(query: VSXQueryOptions): Promise<VSXExtensionRaw | undefined> {
let offset = 0;
let loop = true;
while (loop) {
const queryOptions = {
...query,
offset
};
const results = await this.client.query(queryOptions);
const compatibleExtension = this.getLatestCompatibleExtension(results.extensions);
if (compatibleExtension) {
return compatibleExtension;
}
// Adjust offset by the amount of returned extensions
offset += results.extensions.length;
// Continue querying if there are more extensions available
loop = results.totalSize > offset;
}
return undefined;
}

getLatestCompatibleExtension(extensions: VSXExtensionRaw[]): VSXExtensionRaw | undefined {
if (extensions.length === 0) {
return;
Expand Down
5 changes: 3 additions & 2 deletions dev-packages/ovsx-client/src/ovsx-http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ export class OVSXHttpClient implements OVSXClient {

async query(queryOptions?: VSXQueryOptions): Promise<VSXQueryResult> {
try {
return await this.requestJson(this.buildUrl('api/-/query', queryOptions));
return await this.requestJson(this.buildUrl('api/v2/-/query', queryOptions));
} catch (error) {
console.warn(error);
return {
offset: 0,
totalSize: 0,
extensions: []
};
}
Expand Down
21 changes: 13 additions & 8 deletions dev-packages/ovsx-client/src/ovsx-mock-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,26 @@ export class OVSXMockClient implements OVSXClient {
reviewsUrl: url.extensionReviewsUrl(namespace, name),
timestamp: new Date(now - ids.length + i + 1).toISOString(),
version,
description: `Mock VS Code Extension for ${id}`
description: `Mock VS Code Extension for ${id}`,
namespaceDisplayName: name,
preRelease: false
};
});
return this;
}

async query(queryOptions?: VSXQueryOptions): Promise<VSXQueryResult> {
const extensions = this.extensions
.filter(extension => typeof queryOptions === 'object' && (
this.compare(queryOptions.extensionId, this.id(extension)) &&
this.compare(queryOptions.extensionName, extension.name) &&
this.compare(queryOptions.extensionVersion, extension.version) &&
this.compare(queryOptions.namespaceName, extension.namespace)
));
return {
extensions: this.extensions
.filter(extension => typeof queryOptions === 'object' && (
this.compare(queryOptions.extensionId, this.id(extension)) &&
this.compare(queryOptions.extensionName, extension.name) &&
this.compare(queryOptions.extensionVersion, extension.version) &&
this.compare(queryOptions.namespaceName, extension.namespace)
))
offset: 0,
totalSize: extensions.length,
extensions
};
}

Expand Down
7 changes: 6 additions & 1 deletion dev-packages/ovsx-client/src/ovsx-router-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ export class OVSXRouterClient implements OVSXClient {

protected emptyQueryResult(queryOptions?: VSXQueryOptions): VSXQueryResult {
return {
offset: 0,
totalSize: 0,
extensions: []
};
}
Expand Down Expand Up @@ -183,8 +185,11 @@ export class OVSXRouterClient implements OVSXClient {
results.forEach((result, sourceUri) => {
result.extensions.forEach(extension => filtering.push(this.filterExtension(sourceUri, extension)));
});
const extensions = removeNullValues(await Promise.all(filtering));
return {
extensions: removeNullValues(await Promise.all(filtering))
offset: 0,
totalSize: extensions.length,
extensions
};
}

Expand Down
52 changes: 47 additions & 5 deletions dev-packages/ovsx-client/src/ovsx-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export interface OVSXClient {
*/
search(searchOptions?: VSXSearchOptions): Promise<VSXSearchResult>;
/**
* GET https://openvsx.org/api/-/query
* GET https://openvsx.org/api/v2/-/query
*
* Fetch one or all versions of an extension.
*/
Expand Down Expand Up @@ -110,8 +110,6 @@ export interface VSXSearchResult {
extensions: VSXSearchEntry[];
}

/** @deprecated since 1.31.0 use {@link VSXQueryOptions} instead */
export type VSXQueryParam = VSXQueryOptions;
/**
* The possible options when performing a search.
*
Expand All @@ -126,10 +124,25 @@ export interface VSXQueryOptions {
extensionId?: string;
extensionUuid?: string;
namespaceUuid?: string;
includeAllVersions?: boolean;
includeAllVersions?: boolean | 'links';
targetPlatform?: VSXTargetPlatform;
size?: number;
offset?: number;
}

export type VSXTargetPlatform =
'universal' | 'web' |
'win32-x64' | 'win32-ia32' | 'win32-arm64' |
'darwin-x64' | 'darwin-arm64' |
'linux-x64' | 'linux-arm64' | 'linux-armhf' |
'alpine-x64' | 'alpine-arm64' | (string & {});

export interface VSXQueryResult {
success?: string;
warning?: string;
error?: string;
offset: number;
totalSize: number;
extensions: VSXExtensionRaw[];
}

Expand Down Expand Up @@ -199,12 +212,15 @@ export interface VSXExtensionRaw {
reviewsUrl: string;
name: string;
namespace: string;
publishedBy: VSXUser
targetPlatform?: VSXTargetPlatform;
publishedBy: VSXUser;
preRelease: boolean;
namespaceAccess: VSXExtensionNamespaceAccess;
files: VSXExtensionRawFiles;
allVersions: {
[version: string]: string;
};
allVersionsUrl?: string;
averageRating?: number;
downloadCount: number;
reviewCount: number;
Expand All @@ -213,22 +229,48 @@ export interface VSXExtensionRaw {
preview?: boolean;
verified?: boolean;
displayName?: string;
namespaceDisplayName: string;
description?: string;
categories?: string[];
extensionKind?: string[];
tags?: string[];
license?: string;
homepage?: string;
repository?: string;
sponsorLink?: string;
bugs?: string;
markdown?: string;
galleryColor?: string;
galleryTheme?: string;
localizedLanguages?: string[];
qna?: string;
badges?: VSXBadge[];
dependencies?: VSXExtensionReference[];
bundledExtensions?: VSXExtensionReference[];
allTargetPlatformVersions?: VSXTargetPlatforms[];
url?: string;
engines?: {
[engine: string]: string;
};
}

export interface VSXBadge {
url?: string;
href?: string;
description?: string;
}

export interface VSXExtensionReference {
url: string;
namespace: string;
extension: string;
}

export interface VSXTargetPlatforms {
version: string;
targetPlatforms: VSXTargetPlatform[];
}

export interface VSXResponseError extends Error {
statusCode: number;
}
Expand Down
4 changes: 3 additions & 1 deletion examples/api-samples/src/node/sample-mock-open-vsx-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class SampleMockOpenVsxServer implements BackendApplicationContribution {
app.use(
this.mockServerPath + '/api',
express.Router()
.get('/-/query', async (req, res) => {
.get('/v2/-/query', async (req, res) => {
await this.ready;
res.json(await this.mockClient.query(this.sanitizeQuery(req.query)));
})
Expand Down Expand Up @@ -170,6 +170,8 @@ export class SampleMockOpenVsxServer implements BackendApplicationContribution {
reviewsUrl: url.extensionReviewsUrl(namespace, name),
timestamp: new Date().toISOString(),
version,
namespaceDisplayName: name,
preRelease: false
}
});
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const frontendOnlyApplicationModule = new ContainerModule((bind, unbind,
getExtensionsInfos: async (): Promise<ExtensionInfo[]> => [],
getApplicationInfo: async (): Promise<ApplicationInfo | undefined> => undefined,
getApplicationRoot: async (): Promise<string> => '',
getApplicationPlatform: () => Promise.resolve('web'),
getBackendOS: async (): Promise<OS.Type> => OS.Type.Linux
};
if (isBound(ApplicationServer)) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/common/application-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface ApplicationServer {
getExtensionsInfos(): Promise<ExtensionInfo[]>;
getApplicationInfo(): Promise<ApplicationInfo | undefined>;
getApplicationRoot(): Promise<string>;
getApplicationPlatform(): Promise<string>;
/**
* @deprecated since 1.25.0. Use `OS.backend.type()` instead.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/node/application-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export class ApplicationServerImpl implements ApplicationServer {
return Promise.resolve(this.applicationPackage.projectPath);
}

getApplicationPlatform(): Promise<string> {
return Promise.resolve(`${process.platform}-${process.arch}`);
}

async getBackendOS(): Promise<OS.Type> {
return OS.type();
}
Expand Down
Loading

0 comments on commit 1a5d8d8

Please sign in to comment.