1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Refactor update checking

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Lauri Nevala 2022-01-03 14:33:38 +02:00
parent e5d9de3206
commit c332e2a0d9
12 changed files with 583 additions and 83 deletions

View File

@ -25,6 +25,7 @@ export interface DownloadFileOptions {
url: string;
gzip?: boolean;
timeout?: number;
headers?: request.Headers;
}
export interface DownloadFileTicket<T> {
@ -33,9 +34,9 @@ export interface DownloadFileTicket<T> {
cancel(): void;
}
export function downloadFile({ url, timeout, gzip = true }: DownloadFileOptions): DownloadFileTicket<Buffer> {
export function downloadFile({ url, timeout, gzip = true, headers = {}}: DownloadFileOptions): DownloadFileTicket<Buffer> {
const fileChunks: Buffer[] = [];
const req = request(url, { gzip, timeout });
const req = request(url, { gzip, timeout, headers });
const promise: Promise<Buffer> = new Promise((resolve, reject) => {
req.on("data", (chunk: Buffer) => {
fileChunks.push(chunk);

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { DownloadFileOptions } from "../../common/utils/downloadFile";
import { GitHubVersionChecker } from "../github-latest-version-checker";
const latestRelease = {
tag_name: "v1.0.0",
assets: [{
browser_download_url: "https://foo.bar",
}],
};
describe("GitHubVersionChecker", () => {
describe("getLatestVersion", () => {
it("returns null if homepage does not point to github", async () => {
const checker = new GitHubVersionChecker();
const version = await checker.getLatestVersion({
name: "foo",
version: "1.0.0",
});
expect(version).toBeNull();
});
it("fetches latest release from github", async () => {
const downloadJson = (args: DownloadFileOptions) => {
expect(args).toEqual({
url: "https://api.github.com/repos/lens/extension/releases/latest",
headers: {
"user-agent": "Lens IDE",
},
});
return { promise: new Promise((resolve) => {
resolve(latestRelease);
}) };
};
const checker = new GitHubVersionChecker(downloadJson);
const version = await checker.getLatestVersion({
name: "foo",
version: "0.1.0",
homepage: "https://github.com/lens/extension",
});
expect(version).toEqual({
input: "https://foo.bar",
version: "1.0.0",
});
});
});
});

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { LensExtensionLatestVersionChecker } from "../lens-extension-latest-version-checker";
import { LensExtensionAvailableUpdate, LensExtensionUpdateChecker } from "../lens-extension-update-checker";
class TestLatestVersionChecker implements LensExtensionLatestVersionChecker {
async getLatestVersion(): Promise<LensExtensionAvailableUpdate> {
return {
input: "foo",
version: "1.0.0",
};
}
}
class TestLatestVersionChecker2 implements LensExtensionLatestVersionChecker {
async getLatestVersion(): Promise<LensExtensionAvailableUpdate> {
return {
input: "bar",
version: "0.5.0",
};
}
}
let updateChecker: LensExtensionUpdateChecker;
const versionChecker1 = new TestLatestVersionChecker();
const versionChecker2 = new TestLatestVersionChecker2();
describe("LensExtensionUpdateChecker", () => {
beforeEach(() => {
updateChecker = new LensExtensionUpdateChecker({
foo: versionChecker1,
bar: versionChecker2,
});
});
describe("run", () => {
it("checks latest version from version checker", async () => {
const versionCheckerSpy = jest.spyOn(versionChecker1, "getLatestVersion");
const versionCheckerSpy2 = jest.spyOn(versionChecker2, "getLatestVersion");
await updateChecker.run({
name: "foo-bar",
version: "0.1.1",
});
expect(versionCheckerSpy).toHaveBeenCalled();
expect(versionCheckerSpy2).toHaveBeenCalled();
});
it("returns latest version from version checkers", async () => {
const update = await updateChecker.run({
name: "foo-bar",
version: "0.1.1",
});
expect(update.version).toEqual("1.0.0");
expect(update.input).toEqual("foo");
});
});
});

View File

@ -22,6 +22,7 @@
import { LensExtension } from "../lens-extension";
import { Console } from "console";
import { stdout, stderr } from "process";
import { LensExtensionUpdateChecker } from "../lens-extension-update-checker";
console = new Console(stdout, stderr);
@ -48,4 +49,61 @@ describe("lens extension", () => {
expect(ext.name).toBe("foo-bar");
});
});
describe("checkForUpdate", () => {
it("runs update checker", async () => {
const updateChecker = new LensExtensionUpdateChecker({});
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1",
},
id: "/this/is/fake/package.json",
absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true,
isCompatible: true,
}, updateChecker);
const updateSpy = jest.spyOn(updateChecker, "run");
await ext.checkForUpdate();
expect(updateSpy).toHaveBeenCalledWith({
name: "foo-bar",
version: "0.1.1",
});
});
});
it("returns available update", async () => {
const updateChecker = new LensExtensionUpdateChecker({});
jest.spyOn(updateChecker, "run").mockResolvedValue({
input: "foo",
version: "1.0.0",
});
ext = new LensExtension({
manifest: {
name: "foo-bar",
version: "0.1.1",
},
id: "/this/is/fake/package.json",
absolutePath: "/absolute/fake/",
manifestPath: "/this/is/fake/package.json",
isBundled: false,
isEnabled: true,
isCompatible: true,
}, updateChecker);
const availableUpdate = await ext.checkForUpdate();
expect(availableUpdate).toEqual({
input: "foo",
version: "1.0.0",
});
});
});

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { DownloadFileOptions } from "../../common/utils/downloadFile";
import { NpmJsVersionChecker } from "../npmjs-latest-version.checker";
const npmPackage = {
versions: {
"1.0.0": {
name: "foo",
},
},
};
describe("NpmJsVersionChecker", () => {
describe("getLatestVersion", () => {
it("returns null if versions not present", async () => {
const downloadJson = () => {
return { promise: new Promise((resolve) => {
resolve({});
}) };
};
const checker = new NpmJsVersionChecker(downloadJson);
const version = await checker.getLatestVersion({
name: "foo",
version: "0.1.0",
});
expect(version).toBeNull();
});
it("fetches latest release from npmjs", async () => {
const downloadJson = (args: DownloadFileOptions) => {
expect(args).toEqual({
url: "https://registry.npmjs.com/foo",
});
return { promise: new Promise((resolve) => {
resolve(npmPackage);
}) };
};
const checker = new NpmJsVersionChecker(downloadJson);
const version = await checker.getLatestVersion({
name: "foo",
version: "0.1.0",
});
expect(version).toEqual({
input: "foo",
version: "1.0.0",
});
});
});
});

View File

@ -21,22 +21,23 @@
import { ipcRenderer } from "electron";
import { EventEmitter } from "events";
import _ from "lodash";
import { isEqual } from "lodash";
import { action, computed, makeObservable, observable, observe, reaction, when } from "mobx";
import path from "path";
import { AppPaths } from "../../common/app-paths";
import { broadcastMessage, ipcMainOn, ipcRendererOn, requestMain, ipcMainHandle } from "../../common/ipc";
import { Disposer, downloadJson, toJS } from "../../common/utils";
import { Disposer, toJS } from "../../common/utils";
import logger from "../../main/logger";
import type { KubernetesCluster } from "../common-api/catalog";
import type { InstalledExtension } from "../extension-discovery";
import { ExtensionsStore } from "../extensions-store";
import { GitHubVersionChecker } from "../github-latest-version-checker";
import type { LensExtension, LensExtensionConstructor, LensExtensionId } from "../lens-extension";
import { LensExtensionUpdateChecker } from "../lens-extension-update-checker";
import type { LensMainExtension } from "../lens-main-extension";
import type { LensRendererExtension } from "../lens-renderer-extension";
import { NpmJsVersionChecker } from "../npmjs-latest-version.checker";
import * as registries from "../registries";
import { SemVer } from "semver";
import URLParse from "url-parse";
export function extensionPackagesRoot() {
return path.join(AppPaths.get("userData"));
@ -72,13 +73,20 @@ export class ExtensionLoader {
// emits event "remove" of type LensExtension when the extension is removed
private events = new EventEmitter();
private extensionUpdateSources = {
github: new GitHubVersionChecker(),
npmJs: new NpmJsVersionChecker(),
};
@observable isLoaded = false;
private extensionUpdateChecker: LensExtensionUpdateChecker;
get whenLoaded() {
return when(() => this.isLoaded);
}
constructor() {
this.extensionUpdateChecker = new LensExtensionUpdateChecker(this.extensionUpdateSources);
makeObservable(this);
observe(this.instances, change => {
switch (change.type) {
@ -251,81 +259,15 @@ export class ExtensionLoader {
});
}
async getAvailableExtensionUpdates(): Promise<{ name: string; version: string }[]> {
const availableUpdates: { name: string; version: string }[] = [];
// eslint-disable-next-line unused-imports/no-unused-vars-ts
for (const [_, extension] of this.extensions) {
console.log(`Check for update: ${extension.manifest.name}`);
const availableUpdate = await this.getLatestVersionFromNpmJs(extension) || await this.getLatestVersionFromGithub(extension);
if (availableUpdate) {
if (new SemVer(extension.manifest.version, { loose: true, includePrerelease: true }).compare(availableUpdate.version) === -1) {
extension.availableUpdate = {
version: availableUpdate.version,
input: availableUpdate.updateInput,
};
availableUpdates.push({ name: extension.manifest.name, version: availableUpdate.version });
}
}
}
return availableUpdates;
}
protected async getLatestVersionFromNpmJs(extension: InstalledExtension) {
const name = extension.manifest.name;
const registryUrl = new URLParse("https://registry.npmjs.com").set("pathname", name).toString();
const { promise } = downloadJson({ url: registryUrl });
const json = await promise.catch(() => {
// do nothing
});
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
return null;
}
const versions = Object.keys(json.versions)
.map(version => new SemVer(version, { loose: true, includePrerelease: true }))
// ignore pre-releases for auto picking the version
.filter(version => version.prerelease.length === 0);
const version = _.reduce(versions, (prev, curr) => (
prev.compareMain(curr) === -1
? curr
: prev
)).format();
return {
updateInput: name,
version,
};
}
protected async getLatestVersionFromGithub(extension: InstalledExtension) {
const repo = extension.manifest.homepage?.replace("https://github.com/", "");
const registryUrl = `https://api.github.com/repos/${repo}/releases/latest`;
const { promise } = downloadJson({ url: registryUrl });
const json = await promise.catch(() => {
// do nothing
});
if (!json || json.error || json.prerelease || !json.tag_name) {
return null;
}
return {
updateInput: json.assets[0].browser_download_url,
version: new SemVer(json.tag_name).version,
};
}
loadOnMain() {
this.autoInitExtensions(() => Promise.resolve([]));
this.autoInitExtensions(async (extension: LensMainExtension) => {
// Check for update for the extension on main process that does not have renderer script
if (!extension.manifest.renderer) {
this.checkForExtensionUpdate(extension);
}
return Promise.resolve([]);
});
}
loadOnClusterManagerRenderer() {
@ -352,6 +294,8 @@ export class ExtensionLoader {
}
});
this.checkForExtensionUpdate(extension);
return removeItems;
});
}
@ -386,6 +330,10 @@ export class ExtensionLoader {
});
}
protected async checkForExtensionUpdate(extension: LensExtension) {
this.extensions.get(extension.id).availableUpdate = await extension.checkForUpdate();
}
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Disposer[]>) {
const loadingExtensions: { isBundled: boolean, loaded: Promise<void> }[] = [];
@ -402,7 +350,7 @@ export class ExtensionLoader {
continue;
}
const instance = new LensExtensionClass(extension);
const instance = new LensExtensionClass(extension, this.extensionUpdateChecker);
const loaded = instance.enable(register).catch((err) => {
logger.error(`${logModule}: failed to enable`, { ext: extension, err });

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { SemVer } from "semver";
import logger from "../common/logger";
import { DownloadFileOptions, downloadJson } from "../common/utils";
import type { LensExtensionManifest } from "./lens-extension";
import type { LensExtensionLatestVersionChecker } from "./lens-extension-latest-version-checker";
export class GitHubVersionChecker implements LensExtensionLatestVersionChecker {
protected downloadJson;
constructor(downloadJsonOverride?: (args: DownloadFileOptions) => any) {
this.downloadJson = downloadJsonOverride || downloadJson;
}
public async getLatestVersion(manifest: LensExtensionManifest) {
if (!manifest.homepage?.includes("https://github.com")) {
return null;
}
const repo = manifest.homepage?.replace("https://github.com/", "");
const registryUrl = `https://api.github.com/repos/${repo}/releases/latest`;
const json = await this.getJson(registryUrl);
if (!json || json.error || json.prerelease || !json.tag_name) {
return null;
}
logger.debug(`Found new version (${json.tag_name}) from GitHub`);
return {
input: json.assets[0].browser_download_url,
version: new SemVer(json.tag_name).version,
};
}
protected async getJson(url: string) {
const headers = {
"user-agent": "Lens IDE",
};
const { promise } = this.downloadJson({ url, headers });
const json = await promise.catch(() => {
// do nothing
});
return json;
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { LensExtensionManifest } from "./lens-extension";
import type { LensExtensionAvailableUpdate } from "./lens-extension-update-checker";
export interface LensExtensionLatestVersionChecker {
getLatestVersion(manifest: LensExtensionManifest): Promise<LensExtensionAvailableUpdate>
}

View File

@ -0,0 +1,88 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import _ from "lodash";
import { SemVer } from "semver";
import logger from "../common/logger";
import type { LensExtensionManifest } from "./lens-extension";
import type { LensExtensionLatestVersionChecker } from "./lens-extension-latest-version-checker";
export type LensExtensionAvailableUpdate = {
input: string;
version: string;
};
export class LensExtensionUpdateChecker {
protected updateSources: {
[key: string]: LensExtensionLatestVersionChecker;
};
constructor(updateSources: {
[key: string]: LensExtensionLatestVersionChecker;
}) {
this.updateSources = updateSources;
}
public async run(manifest: LensExtensionManifest): Promise<LensExtensionAvailableUpdate|undefined> {
const { name, version } = manifest;
logger.debug(`Check update for extension ${name}`);
const versions: LensExtensionAvailableUpdate[] = [];
for(const checker of Object.values(this.updateSources)) {
const latestVersionFromSource = await checker.getLatestVersion(manifest);
if (latestVersionFromSource && this.isUpdate(version, latestVersionFromSource.version)) {
versions.push(latestVersionFromSource);
}
}
const latestVersion = this.getLatestVersion(versions);
if (latestVersion) {
logger.debug(`Found new version ${latestVersion}`);
}
return latestVersion;
}
private isUpdate(currentVersion: string, availableVersion: string) {
return new SemVer(currentVersion, { loose: true, includePrerelease: true }).compare(availableVersion) === -1;
}
private getLatestVersion(versions: LensExtensionAvailableUpdate[]) {
if (versions.length === 0) {
return null;
}
return _.reduce(versions, (prev, curr) => {
const previousVersion = new SemVer(prev.version, { loose: true, includePrerelease: true });
const currentVersion = new SemVer(curr.version, { loose: true, includePrerelease: true });
return previousVersion.compareMain(currentVersion) === -1
? curr
: prev;
});
}
}

View File

@ -26,6 +26,7 @@ import logger from "../main/logger";
import type { ProtocolHandlerRegistration } from "./registries";
import type { PackageJson } from "type-fest";
import { Disposer, disposer } from "../common/utils";
import type { LensExtensionUpdateChecker } from "./lens-extension-update-checker";
export type LensExtensionId = string; // path to manifest (package.json)
export type LensExtensionConstructor = new (...args: ConstructorParameters<typeof LensExtension>) => LensExtension;
@ -45,6 +46,8 @@ export class LensExtension {
readonly manifestPath: string;
readonly isBundled: boolean;
private updateChecker: LensExtensionUpdateChecker;
protocolHandlers: ProtocolHandlerRegistration[] = [];
@observable private _isEnabled = false;
@ -55,12 +58,14 @@ export class LensExtension {
[Disposers] = disposer();
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) {
constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension, updateChecker?: LensExtensionUpdateChecker) {
makeObservable(this);
this.id = id;
this.manifest = manifest;
this.manifestPath = manifestPath;
this.isBundled = !!isBundled;
this.updateChecker = updateChecker;
}
get name() {
@ -120,6 +125,10 @@ export class LensExtension {
}
}
public async checkForUpdate() {
return this.updateChecker?.run(this.manifest);
}
protected onActivate(): Promise<void> | void {
return;
}

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import _ from "lodash";
import { SemVer } from "semver";
import URLParse from "url-parse";
import { DownloadFileOptions, downloadJson } from "../common/utils/";
import type { LensExtensionManifest } from "./lens-extension";
import type { LensExtensionLatestVersionChecker } from "./lens-extension-latest-version-checker";
export class NpmJsVersionChecker implements LensExtensionLatestVersionChecker {
protected downloadJson;
constructor(downloadJsonOverride?: (args: DownloadFileOptions) => any) {
this.downloadJson = downloadJsonOverride || downloadJson;
}
public async getLatestVersion(manifest: LensExtensionManifest) {
const { name } = manifest;
const registryUrl = new URLParse("https://registry.npmjs.com").set("pathname", name).toString();
const json = await this.getJson(registryUrl);
if (!json || json.error || typeof json.versions !== "object" || !json.versions) {
return null;
}
// TODO refactor into helpoer method
const versions = Object.keys(json.versions)
.map(version => new SemVer(version, { loose: true, includePrerelease: true }))
// ignore pre-releases for auto picking the version
.filter(version => version.prerelease.length === 0);
const version = _.reduce(versions, (prev, curr) => (
prev.compareMain(curr) === -1
? curr
: prev
)).format();
return {
input: name,
version,
};
}
protected async getJson(url: string) {
const { promise } = this.downloadJson({ url });
const json = await promise.catch(() => {
// do nothing
});
return json;
}
}

View File

@ -281,7 +281,6 @@ app.on("ready", async () => {
});
extensionLoader.initExtensions(extensions);
extensionLoader.getAvailableExtensionUpdates();
} catch (error) {
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
console.error(error);