mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add auto-update and pre-release update user settings
- Add settings in user preferences for auto-updating (default false) and for allowing pre-release versions (default false) - Use in-Lens notifications instead of OS notifications as those were found to be flaky - Add rudimentary main->renderer notification system. Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
1a4b6cddb5
commit
cd154a8343
@ -162,6 +162,7 @@
|
||||
"@hapi/subtext": "^7.0.3",
|
||||
"@kubernetes/client-node": "^0.12.0",
|
||||
"@types/crypto-js": "^3.1.47",
|
||||
"@types/dateformat": "^3.0.1",
|
||||
"@types/electron-window-state": "^2.0.34",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
@ -171,6 +172,7 @@
|
||||
"@types/marked": "^0.7.4",
|
||||
"@types/mock-fs": "^4.10.0",
|
||||
"@types/node": "^12.12.45",
|
||||
"@types/node-notifier": "^8.0.0",
|
||||
"@types/proper-lockfile": "^4.1.1",
|
||||
"@types/react-beautiful-dnd": "^13.0.0",
|
||||
"@types/tar": "^4.0.3",
|
||||
@ -178,6 +180,7 @@
|
||||
"chalk": "^4.1.0",
|
||||
"conf": "^7.0.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"dateformat": "^4.3.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"file-type": "^14.7.1",
|
||||
@ -196,6 +199,7 @@
|
||||
"mobx-observable-history": "^1.0.3",
|
||||
"mock-fs": "^4.12.0",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"node-notifier": "^9.0.0",
|
||||
"node-pty": "^0.9.0",
|
||||
"openid-client": "^3.15.2",
|
||||
"path-to-regexp": "^6.1.0",
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
import { ipcMain, ipcRenderer, WebContents, webContents } from "electron"
|
||||
import logger from "../main/logger";
|
||||
|
||||
export const NotificationChannelPrefix: IpcChannel = "notications:";
|
||||
export const NotificationChannelAdd: IpcChannel = `${NotificationChannelPrefix}add`;
|
||||
|
||||
export type IpcChannel = string;
|
||||
|
||||
export interface IpcChannelOptions {
|
||||
@ -50,7 +53,7 @@ export function createIpcChannel({ autoBind = true, once, timeout = 0, handle, c
|
||||
return ipcChannel;
|
||||
}
|
||||
|
||||
export interface IpcBroadcastParams<A extends any[] = any> {
|
||||
export interface IpcBroadcastParams<A extends any[] = any[]> {
|
||||
channel: IpcChannel
|
||||
webContentId?: number; // send to single webContents view
|
||||
frameId?: number; // send to inner frame of webContents
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ThemeId } from "../renderer/theme.store";
|
||||
import { app, remote } from 'electron';
|
||||
import semver from "semver"
|
||||
import { readFile } from "fs-extra"
|
||||
import { action, observable, reaction, toJS } from "mobx";
|
||||
import { action, IReactionDisposer, observable, reaction, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import migrations from "../migrations/user-store"
|
||||
import { getAppVersion } from "./utils/app-version";
|
||||
@ -27,6 +27,8 @@ export interface UserPreferences {
|
||||
downloadKubectlBinaries?: boolean;
|
||||
downloadBinariesPath?: string;
|
||||
kubectlBinariesPath?: string;
|
||||
allowAutoUpdates?: boolean;
|
||||
allowPrereleaseVersions?: boolean;
|
||||
}
|
||||
|
||||
export class UserStore extends BaseStore<UserStoreModel> {
|
||||
@ -59,6 +61,8 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
||||
colorTheme: UserStore.defaultTheme,
|
||||
downloadMirror: "default",
|
||||
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
|
||||
allowAutoUpdates: false,
|
||||
allowPrereleaseVersions: false,
|
||||
};
|
||||
|
||||
get isNewVersion() {
|
||||
|
||||
@ -1,19 +1,188 @@
|
||||
import { autoUpdater } from "electron-updater"
|
||||
import logger from "./logger"
|
||||
import { autoUpdater, UpdateInfo } from "electron-updater";
|
||||
import { autorun } from "mobx";
|
||||
import { userStore } from "../common/user-store";
|
||||
import logger from "./logger";
|
||||
import dateFormat from "dateformat";
|
||||
import { broadcastIpc, IpcChannel, NotificationChannelAdd, NotificationChannelPrefix } from "../common/ipc";
|
||||
import { ipcMain } from "electron";
|
||||
import { isDevelopment } from "../common/vars";
|
||||
import { SemVer } from "semver";
|
||||
|
||||
export default class AppUpdater {
|
||||
function delay(duration: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, duration));
|
||||
}
|
||||
|
||||
protected updateInterval: number = (1000 * 60 * 60 * 24) // once a day
|
||||
class NotificationBackchannel {
|
||||
private static _id = 0;
|
||||
|
||||
constructor() {
|
||||
autoUpdater.logger = logger
|
||||
}
|
||||
|
||||
public start() {
|
||||
setInterval(() => {
|
||||
autoUpdater.checkForUpdatesAndNotify()
|
||||
}, this.updateInterval)
|
||||
|
||||
return autoUpdater.checkForUpdatesAndNotify()
|
||||
static nextId(): IpcChannel {
|
||||
return `${NotificationChannelPrefix}${NotificationBackchannel._id++}`
|
||||
}
|
||||
}
|
||||
|
||||
const title = "Lens Updater";
|
||||
|
||||
async function autoUpdateNow(): Promise<void> {
|
||||
const body = "Downloading and installing update.";
|
||||
broadcastIpc({
|
||||
channel: NotificationChannelAdd,
|
||||
args: [{
|
||||
title,
|
||||
body,
|
||||
status: "info",
|
||||
timeout: 5000,
|
||||
}]
|
||||
})
|
||||
|
||||
logger.info("[UPDATE CHECKER]: update downloaded started");
|
||||
await autoUpdater.downloadUpdate();
|
||||
logger.info("[UPDATE CHECKER]: update downloadeded");
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
async function autoUpdateCheck(args: UpdateInfo): Promise<void> {
|
||||
return new Promise(async resolve => {
|
||||
const body = "Install and restart Lens?";
|
||||
const yesNowChannel = NotificationBackchannel.nextId();
|
||||
const yesLaterChannel = NotificationBackchannel.nextId();
|
||||
const noChannel = NotificationBackchannel.nextId();
|
||||
|
||||
function cleanupChannels() {
|
||||
ipcMain.removeAllListeners(yesNowChannel);
|
||||
ipcMain.removeAllListeners(yesLaterChannel);
|
||||
ipcMain.removeAllListeners(noChannel);
|
||||
}
|
||||
|
||||
ipcMain
|
||||
.on(yesNowChannel, async () => {
|
||||
cleanupChannels();
|
||||
|
||||
await autoUpdater.downloadUpdate();
|
||||
autoUpdater.quitAndInstall();
|
||||
|
||||
resolve();
|
||||
})
|
||||
.on(yesLaterChannel, async () => {
|
||||
cleanupChannels();
|
||||
|
||||
await autoUpdater.downloadUpdate();
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
resolve();
|
||||
})
|
||||
.on(noChannel, () => {
|
||||
cleanupChannels();
|
||||
resolve();
|
||||
});
|
||||
|
||||
broadcastIpc({
|
||||
channel: NotificationChannelAdd,
|
||||
args: [{
|
||||
title,
|
||||
body,
|
||||
status: "info",
|
||||
buttons: [
|
||||
{
|
||||
label: "Yes, now",
|
||||
backchannel: yesNowChannel,
|
||||
style: {
|
||||
background: "green",
|
||||
marginRight: "10px"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Yes, later",
|
||||
backchannel: yesLaterChannel,
|
||||
style: {
|
||||
background: "green",
|
||||
marginRight: "10px"
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "No",
|
||||
backchannel: noChannel,
|
||||
accent: true
|
||||
}
|
||||
],
|
||||
closeChannel: noChannel,
|
||||
}]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the automatic update checking
|
||||
* @param interval milliseconds between interval to check on, defaulkts to 24h
|
||||
*/
|
||||
export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void {
|
||||
if (isDevelopment) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.logger = logger;
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
|
||||
/**
|
||||
* GC saftey: This function's lifetime is the lifetime of the application.
|
||||
* So no need to call the disposer.
|
||||
*/
|
||||
autorun(() => {
|
||||
autoUpdater.autoDownload = userStore.preferences.allowAutoUpdates;
|
||||
autoUpdater.allowPrerelease = userStore.preferences.allowPrereleaseVersions;
|
||||
});
|
||||
|
||||
autoUpdater
|
||||
.on("update-available", async (args: UpdateInfo) => {
|
||||
try {
|
||||
const releaseDate = new Date(args.releaseDate);
|
||||
const body = `Version ${args.version} was release on ${dateFormat(releaseDate, "dddd, mmmm dS, yyyy")}.`;
|
||||
broadcastIpc({
|
||||
channel: NotificationChannelAdd,
|
||||
args: [{
|
||||
title,
|
||||
body,
|
||||
status: "info",
|
||||
timeout: 5000,
|
||||
}]
|
||||
});
|
||||
|
||||
const version = new SemVer(args.version);
|
||||
|
||||
if (userStore.preferences.allowAutoUpdates && version.prerelease !== null) {
|
||||
// don't auto update to pre-release versions.
|
||||
await autoUpdateNow();
|
||||
} else {
|
||||
await autoUpdateCheck(args);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("[UPDATE CHECKER]: notification failed", { error: String(error) })
|
||||
}
|
||||
})
|
||||
.on("update-not-available", () => {
|
||||
try {
|
||||
const stream = userStore.preferences.allowPrereleaseVersions ? "prerelease" : "stable";
|
||||
const body = `Lens is running the latest ${stream} version.`;
|
||||
broadcastIpc({
|
||||
channel: NotificationChannelAdd,
|
||||
args: [{
|
||||
title,
|
||||
body,
|
||||
status: "info",
|
||||
timeout: 5000,
|
||||
}]
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error("[UPDATE CHECKER]: notification failed", { error: String(error) })
|
||||
}
|
||||
})
|
||||
|
||||
async function helper() {
|
||||
while (true) {
|
||||
await autoUpdater.checkForUpdates();
|
||||
await delay(interval);
|
||||
}
|
||||
}
|
||||
|
||||
helper()
|
||||
.catch(error => logger.error("[UPDATE CHECKER]: failed with an error", { error: String(error) }));
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import path from "path"
|
||||
import { LensProxy } from "./lens-proxy"
|
||||
import { WindowManager } from "./window-manager";
|
||||
import { ClusterManager } from "./cluster-manager";
|
||||
import AppUpdater from "./app-updater"
|
||||
import { startUpdateChecking } from "./app-updater"
|
||||
import { shellSync } from "./shell-sync"
|
||||
import { getFreePort } from "./port"
|
||||
import { mangleProxyEnv } from "./proxy-env"
|
||||
@ -39,8 +39,6 @@ async function main() {
|
||||
logger.info(`🚀 Starting Lens from "${workingDir}"`)
|
||||
|
||||
tracker.event("app", "start");
|
||||
const updater = new AppUpdater()
|
||||
updater.start();
|
||||
|
||||
registerFileProtocol("static", __static);
|
||||
|
||||
@ -75,6 +73,13 @@ async function main() {
|
||||
|
||||
// create window manager and open app
|
||||
windowManager = new WindowManager(proxyPort);
|
||||
|
||||
/**
|
||||
* This depends on:
|
||||
* 1. userStore: it reads the user's auto update settings
|
||||
* 2. windowManager: it will send IPC to the main window for notifications
|
||||
*/
|
||||
startUpdateChecking();
|
||||
}
|
||||
|
||||
app.on("ready", main);
|
||||
|
||||
@ -94,7 +94,7 @@ export class KubeAuthProxy {
|
||||
protected async sendIpcLogMessage(res: KubeAuthProxyLog) {
|
||||
const channel = `kube-auth:${this.cluster.id}`
|
||||
logger.info(`[KUBE-AUTH]: out-channel "${channel}"`, { ...res, meta: this.cluster.getMeta() });
|
||||
broadcastIpc({ channel: channel, args: [res] });
|
||||
broadcastIpc({ channel, args: [res] });
|
||||
}
|
||||
|
||||
public exit() {
|
||||
|
||||
@ -73,6 +73,10 @@ export class WindowManager {
|
||||
}
|
||||
}
|
||||
|
||||
send(channel: string, ...data: any[]) {
|
||||
this.mainView.webContents.send(channel, ...data);
|
||||
}
|
||||
|
||||
async showMain() {
|
||||
try {
|
||||
await this.showSplash();
|
||||
|
||||
@ -9,6 +9,7 @@ import { i18nStore } from "./i18n";
|
||||
import { themeStore } from "./theme.store";
|
||||
import { App } from "./components/app";
|
||||
import { LensApp } from "./lens-app";
|
||||
import { notificationsStore } from "./components/notifications/notifications.store";
|
||||
|
||||
type AppComponent = React.ComponentType & {
|
||||
init?(): Promise<void>;
|
||||
@ -30,6 +31,10 @@ export async function bootstrap(App: AppComponent) {
|
||||
// Register additional store listeners
|
||||
clusterStore.registerIpcListener();
|
||||
|
||||
if (process.isMainFrame) {
|
||||
notificationsStore.registerIpcListener();
|
||||
}
|
||||
|
||||
// init app's dependencies if any
|
||||
if (App.init) {
|
||||
await App.init();
|
||||
|
||||
@ -194,6 +194,24 @@ export class Preferences extends React.Component {
|
||||
<small className="hint">
|
||||
<Trans>Telemetry & usage data is collected to continuously improve the Lens experience.</Trans>
|
||||
</small>
|
||||
|
||||
<h2><Trans>Updates</Trans></h2>
|
||||
<Checkbox
|
||||
label={<Trans>Allow auto updates</Trans>}
|
||||
value={preferences.allowAutoUpdates}
|
||||
onChange={v => preferences.allowAutoUpdates = v}
|
||||
/>
|
||||
<small className="hint">
|
||||
<Trans>Allow Lens to auto update itself to the latest version. Lens checks on startup and then once a day.</Trans>
|
||||
</small>
|
||||
<Checkbox
|
||||
label={<Trans>Allow pre-release versions of Lens</Trans>}
|
||||
value={preferences.allowPrereleaseVersions}
|
||||
onChange={v => preferences.allowPrereleaseVersions = v}
|
||||
/>
|
||||
<small className="hint">
|
||||
<Trans>Allow upgrading Lens to pre-release versions. This means that the update checker will ask about pre release versions but won't auto upgrade to them.</Trans>
|
||||
</small>
|
||||
</WizardLayout>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -4,6 +4,10 @@ import { autobind } from "../../utils";
|
||||
import isObject from "lodash/isObject"
|
||||
import uniqueId from "lodash/uniqueId";
|
||||
import { JsonApiErrorParsed } from "../../api/json-api";
|
||||
import logger from "../../../main/logger";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcChannel, NotificationChannelAdd } from "../../../common/ipc";
|
||||
import { Button, ButtonProps } from "../button";
|
||||
|
||||
export type IMessageId = string | number;
|
||||
export type IMessage = React.ReactNode | React.ReactNode[] | JsonApiErrorParsed;
|
||||
@ -19,6 +23,39 @@ export interface INotification {
|
||||
message: IMessage;
|
||||
status?: NotificationStatus;
|
||||
timeout?: number; // auto-hiding timeout in milliseconds, 0 = no hide
|
||||
onClose?(): void; // additonal logic on when the notification times out or is closed by the "x"
|
||||
}
|
||||
|
||||
export interface MainNotification {
|
||||
title: string;
|
||||
body: string;
|
||||
buttons?: ({
|
||||
backchannel: IpcChannel;
|
||||
} & ButtonProps)[];
|
||||
status: NotificationStatus;
|
||||
timeout?: number;
|
||||
closeChannel?: IpcChannel;
|
||||
}
|
||||
|
||||
function renderButtons(id: IpcChannel, buttons?: MainNotification["buttons"]): React.ReactNode {
|
||||
if (!buttons) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<br />
|
||||
<div className="flex row align-right box grow">
|
||||
{buttons.map(({ backchannel, ...props}) => (
|
||||
<Button {...props} onClick={() => {
|
||||
console.log(`sending to ${backchannel}`)
|
||||
ipcRenderer.send(backchannel);
|
||||
notificationsStore.remove(id);
|
||||
}} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@autobind()
|
||||
@ -27,6 +64,29 @@ export class NotificationsStore {
|
||||
|
||||
protected autoHideTimers = new Map<IMessageId, number>();
|
||||
|
||||
registerIpcListener(): void {
|
||||
logger.info(`[NOTIFICATION-STORE] start to listen for notifications requests from main`);
|
||||
ipcRenderer.on(NotificationChannelAdd, (event, model: MainNotification) => {
|
||||
console.log(model);
|
||||
const id = uniqueId("notification_");
|
||||
this.add({
|
||||
message: (
|
||||
<>
|
||||
<b>{model.title}</b>
|
||||
<p>{model.body}</p>
|
||||
{renderButtons(id, model.buttons)}
|
||||
</>
|
||||
),
|
||||
id,
|
||||
status: model.status,
|
||||
timeout: model.timeout,
|
||||
onClose: () => {
|
||||
model.closeChannel && ipcRenderer.send(model.closeChannel);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
addAutoHideTimer(notification: INotification) {
|
||||
this.removeAutoHideTimer(notification);
|
||||
const { id, timeout } = notification;
|
||||
@ -84,7 +84,10 @@ export class Notifications extends React.Component {
|
||||
<div className="box center">
|
||||
<Icon
|
||||
material="close" className="close"
|
||||
onClick={prevDefault(() => remove(notification))}
|
||||
onClick={prevDefault(() => {
|
||||
remove(notification);
|
||||
notification.onClose?.();
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -29,12 +29,8 @@ export class ThemeStore {
|
||||
{ id: "kontena-light", type: ThemeType.LIGHT },
|
||||
];
|
||||
|
||||
@computed get activeThemeId() {
|
||||
return userStore.preferences.colorTheme;
|
||||
}
|
||||
|
||||
@computed get activeTheme(): Theme {
|
||||
const activeTheme = this.themes.find(theme => theme.id === this.activeThemeId) || this.themes[0];
|
||||
const activeTheme = this.themes.find(theme => theme.id === userStore.preferences.colorTheme) || this.themes[0];
|
||||
return {
|
||||
colors: {},
|
||||
...activeTheme,
|
||||
@ -43,9 +39,9 @@ export class ThemeStore {
|
||||
|
||||
constructor() {
|
||||
// auto-apply active theme
|
||||
reaction(() => this.activeThemeId, async themeId => {
|
||||
reaction(() => this.activeTheme, async ({ id }) => {
|
||||
try {
|
||||
await this.loadTheme(themeId);
|
||||
await this.loadTheme(id);
|
||||
this.applyTheme();
|
||||
} catch (err) {
|
||||
userStore.resetTheme();
|
||||
|
||||
@ -159,4 +159,4 @@ export default function (): webpack.Configuration {
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@ -1723,6 +1723,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-3.1.47.tgz#36e549dd3f1322742a3a738e7c113ebe48221860"
|
||||
integrity sha512-eI6gvpcGHLk3dAuHYnRCAjX+41gMv1nz/VP55wAe5HtmAKDOoPSfr3f6vkMc08ov1S0NsjvUBxDtHHxqQY1LGA==
|
||||
|
||||
"@types/dateformat@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/dateformat/-/dateformat-3.0.1.tgz#98d747a2e5e9a56070c6bf14e27bff56204e34cc"
|
||||
integrity sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==
|
||||
|
||||
"@types/debug@^4.1.5":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
|
||||
@ -1966,6 +1971,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node-notifier@^8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-notifier/-/node-notifier-8.0.0.tgz#51100d67155ed1500a8aaa633987109f59a0637d"
|
||||
integrity sha512-CseIDQOC/I+yvj/4ItpG4ATcwooQlGPDDJweII8nspjjZg4ZBuvkyHg9P81QkElgU9FpYlb5A27BRggD3idTCQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*":
|
||||
version "14.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3"
|
||||
@ -4299,6 +4311,11 @@ date-fns@^2.0.1, date-fns@^2.14.0:
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.14.0.tgz#359a87a265bb34ef2e38f93ecf63ac453f9bc7ba"
|
||||
integrity sha512-1zD+68jhFgDIM0rF05rcwYO8cExdNqxjq4xP1QKM60Q45mnO6zaMWB4tOzrIr4M4GSLntsKeE4c9Bdl2jhL/yw==
|
||||
|
||||
dateformat@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.3.1.tgz#e010ca5915f0c7d47e5b4e4287dd5ecb41125a96"
|
||||
integrity sha512-xhq1wI5BQ0TMJDvio0BLP8lNeYlhAvmh/7H52H9n6kfzqSmRpIhH5KEIjJ7onFEAh5CQVrAP2MAG8wZ6j0BKzQ==
|
||||
|
||||
debounce-fn@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
|
||||
@ -6760,7 +6777,7 @@ is-wsl@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
|
||||
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
|
||||
|
||||
is-wsl@^2.1.1:
|
||||
is-wsl@^2.1.1, is-wsl@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
||||
@ -8419,6 +8436,18 @@ node-notifier@^7.0.0:
|
||||
uuid "^7.0.3"
|
||||
which "^2.0.2"
|
||||
|
||||
node-notifier@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-9.0.0.tgz#46c5bbecbb796d4a803f646cea5bc91403f2ff38"
|
||||
integrity sha512-SkwNwGnMMlSPrcoeH4CSo9XyWe72acAHEJGDdPdB+CyBVHsIYaTQ4U/1wk3URsyzC75xZLg2vzU2YaALlqDF1Q==
|
||||
dependencies:
|
||||
growly "^1.3.0"
|
||||
is-wsl "^2.2.0"
|
||||
semver "^7.3.2"
|
||||
shellwords "^0.1.1"
|
||||
uuid "^8.3.0"
|
||||
which "^2.0.2"
|
||||
|
||||
node-pty@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
|
||||
@ -11758,6 +11787,11 @@ uuid@^8.1.0:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
|
||||
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
|
||||
|
||||
uuid@^8.3.0:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
v8-compile-cache@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user