Merge remote-tracking branch 'thenextwave/main' into wave8

This commit is contained in:
sawka
2024-09-19 13:54:19 -07:00
38 changed files with 313 additions and 1096 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./assets/waveterm-logo-horizontal-dark.png">
<source media="(prefers-color-scheme: light)" srcset="./assets/waveterm-logo-horizontal-light.png">
<img alt="Wave Terminal Logo" src="./assets/waveterm-logo-horizontal-light.png" width="240" height="80" style="max-width: 100%;">
<source media="(prefers-color-scheme: dark)" srcset="./assets/wave-dark.png">
<source media="(prefers-color-scheme: light)" srcset="./assets/wave-light.png">
<img alt="Wave Terminal Logo" src="./assets/wave-light.png" width="240">
</picture>
<br/>
</p>
@@ -25,7 +25,7 @@ Install Wave Terminal from: [www.waveterm.dev/download](https://www.waveterm.dev
Also available as a homebrew cask for MacOS:
```
```bash
brew install --cask wave
```
+9 -4
View File
@@ -3,9 +3,14 @@
## Step-by-step guide
1. Go to the [Actions tab](https://github.com/wavetermdev/thenextwave/actions) and select "Bump Version" from the left sidebar.
2. Click on "Run workflow". You will see two options:
- "SemVer Bump": This defaults to `none`. Adjust this if you want to increment the version number according to semantic versioning rules (`patch`, `minor`, `major`).
- "Is Prerelease": This defaults to `true`. If set to `true`, a `-beta.X` version will be appended to the end of the version. If one is already present and the base SemVer is not being incremented, the `-beta` version will be incremented (i.e. `0.1.13-beta.0` to `0.1.13-beta.1`).
2. Click on "Run workflow".
- You will see two options:
- "SemVer Bump": This defaults to `none`. Adjust this if you want to increment the version number according to semantic versioning rules (`patch`, `minor`, `major`).
- "Is Prerelease": This defaults to `true`. If set to `true`, a `-beta.X` version will be appended to the end of the version. If one is already present and the base SemVer is not being incremented, the `-beta` version will be incremented (i.e. `0.1.13-beta.0` to `0.1.13-beta.1`). If set to `false`, the `-beta.X` suffix will be removed from the version number. If one was not already present, it will remain absent.
- Some examples:
- If you are creating a new prerelease following an official release, you would set "SemVer Bump" to to the expected version bump (`patch`, `minor`, or `major`) and "Is Prerelease" to `true`.
- If you are bumping an existing prerelease to a new prerelease under the same version, you would set "SemVer Bump" to `none` and "Is Prerelease" to `true`.
- If you are promoting a prerelease version to an official release, you would set "SemVer Bump" to `none` and "Is Prerelease" to `false`.
3. After "Bump Version" a "Build Helper" run will kick off automatically for the new version. When this completes, it will generate a draft GitHub Release with all the built artifacts.
4. Review the artifacts in the release and test them locally.
5. When you are confident that the build is good, edit the GitHub Release to add a changelog and release summary and publish the release.
@@ -29,7 +34,7 @@ Our release builds are managed by the [Build Helper workflow](https://github.com
Under the hood, this will call the `package` task in [`Taskfile.yml`](../../Taskfile.yml), which will build the `wavesrv` and `wsh` binaries, then the frontend and Electron codebases using Vite, then it will call `electron-builder` to generate the distributable app packages. The configuration for `electron-builder` is defined in [`electron-builder.config.cjs`](../../electron-builder.config.cjs).
This will also sign and notarize the macOS app package.
This will also sign and notarize the macOS app packages and sign the Windows packages.
Once a build is complete, it will be placed in `s3://waveterm-github-artifacts/staging-w2/<version>`. It can be downloaded for testing using the `artifacts:download:*` task. When you are ready to publish the artifacts to the public release feed, use the `artifacts:publish:*` task to directly copy the artifacts from the staging bucket to the releases bucket.
+1 -10
View File
@@ -203,16 +203,7 @@ tasks:
- frontend/app/store/wshserver.ts
version:
desc: Get the current package version, or bump version if args are present. To pass args to `version.cjs`, add them after `--`.
summary: |
If no arguments are present, the current version will be returned.
If only a single argument is given, the following are valid inputs:
- `none`: No-op.
- `patch`: Bumps the patch version.
- `minor`: Bumps the minor version.
- `major`: Bumps the major version.
- '1', 'true': Bumps the prerelease version.
If two arguments are given, the first argument must be either `none`, `patch`, `minor`, or `major`. The second argument must be `1` or `true` to bump the prerelease version.
desc: Get the current package version, or bump version if args are present. To pass args to `version.cjs`, add them after `--`. See `version.cjs` for usage definitions for the arguments.
cmd: node version.cjs {{.CLI_ARGS}}
artifacts:upload:
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+4
View File
@@ -26,6 +26,10 @@ export default defineConfig({
"@": "frontend",
},
},
define: {
"process.env.WS_NO_BUFFER_UTIL": "true",
"process.env.WS_NO_UTF_8_VALIDATE": "true",
},
},
preload: {
root: ".",
+6
View File
@@ -29,3 +29,9 @@ export class ElectronWshClientType extends WshClient {
return rtn;
}
}
export let ElectronWshClient: ElectronWshClientType;
export function initElectronWshClient() {
ElectronWshClient = new ElectronWshClientType();
}
+4 -4
View File
@@ -20,7 +20,7 @@ import { fetch } from "../frontend/util/fetchutil";
import * as keyutil from "../frontend/util/keyutil";
import { fireAndForget } from "../frontend/util/util";
import { AuthKey, AuthKeyEnv, configureAuthKeyRequestInjection } from "./authkey";
import { ElectronWshClientType } from "./emain-wsh";
import { ElectronWshClient, initElectronWshClient } from "./emain-wsh";
import { getAppMenu } from "./menu";
import {
getElectronAppBasePath,
@@ -35,7 +35,6 @@ import {
} from "./platform";
import { configureAutoUpdater, updater } from "./updater";
let ElectronWshClient = new ElectronWshClientType();
const electronApp = electron.app;
let WaveVersion = "unknown"; // set by WAVESRV-ESTART
let WaveBuildTime = 0; // set by WAVESRV-ESTART
@@ -858,13 +857,14 @@ async function appMain() {
await electronApp.whenReady();
configureAuthKeyRequestInjection(electron.session.defaultSession);
await relaunchBrowserWindows();
await configureAutoUpdater();
setTimeout(runActiveTimer, 5000); // start active timer, wait 5s just to be safe
try {
initElectronWshrpc(ElectronWshClient, AuthKey);
initElectronWshClient();
initElectronWshrpc(ElectronWshClient, { authKey: AuthKey });
} catch (e) {
console.log("error initializing wshrpc", e);
}
await configureAutoUpdater();
globalIsStarting = false;
+1
View File
@@ -27,6 +27,7 @@ contextBridge.exposeInMainWorld("api", {
ipcRenderer.on("fullscreen-change", (_event, isFullScreen) => callback(isFullScreen)),
onUpdaterStatusChange: (callback) => ipcRenderer.on("app-update-status", (_event, status) => callback(status)),
getUpdaterStatus: () => ipcRenderer.sendSync("get-app-update-status"),
getUpdaterChannel: () => ipcRenderer.sendSync("get-updater-channel"),
installAppUpdate: () => ipcRenderer.send("install-app-update"),
onMenuItemAbout: (callback) => ipcRenderer.on("menu-item-about", callback),
updateWindowControlsOverlay: (rect) => ipcRenderer.send("update-window-controls-overlay", rect),
+33 -5
View File
@@ -1,14 +1,37 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { RpcApi } from "@/app/store/wshclientapi";
import { BrowserWindow, dialog, ipcMain, Notification } from "electron";
import { autoUpdater } from "electron-updater";
import { readFileSync } from "fs";
import path from "path";
import YAML from "yaml";
import { FileService } from "../frontend/app/store/services";
import { isDev } from "../frontend/util/isdev";
import { fireAndForget } from "../frontend/util/util";
import { ElectronWshClient } from "./emain-wsh";
export let updater: Updater;
function getUpdateChannel(settings: SettingsType): string {
const updaterConfigPath = path.join(process.resourcesPath!, "app-update.yml");
const updaterConfig = YAML.parse(readFileSync(updaterConfigPath, { encoding: "utf8" }).toString());
console.log("Updater config from binary:", updaterConfig);
const updaterChannel: string = updaterConfig.channel ?? "latest";
const settingsChannel = settings["autoupdate:channel"];
let retVal = settingsChannel;
// If the user setting doesn't exist yet, set it to the value of the updater config.
if (!settingsChannel) {
console.log("Update channel setting does not exist, setting to value from updater config.");
RpcApi.SetConfigCommand(ElectronWshClient, { "autoupdate:channel": updaterChannel });
retVal = updaterChannel;
}
console.log("Update channel:", retVal);
return retVal;
}
export class Updater {
autoCheckInterval: NodeJS.Timeout | null;
intervalms: number;
@@ -20,7 +43,9 @@ export class Updater {
constructor(settings: SettingsType) {
this.intervalms = settings["autoupdate:intervalms"];
console.log("Update check interval in milliseconds:", this.intervalms);
this.autoCheckEnabled = settings["autoupdate:enabled"];
console.log("Update check enabled:", this.autoCheckEnabled);
this._status = "up-to-date";
this.lastUpdateCheck = new Date(0);
@@ -28,12 +53,10 @@ export class Updater {
this.availableUpdateReleaseName = null;
autoUpdater.autoInstallOnAppQuit = settings["autoupdate:installonquit"];
console.log("Install update on quit:", settings["autoupdate:installonquit"]);
// Only update the release channel if it's specified, otherwise use the one configured in the artifact.
const channel = settings["autoupdate:channel"];
if (channel) {
autoUpdater.channel = channel;
}
// Only update the release channel if it's specified, otherwise use the one configured in the updater.
autoUpdater.channel = getUpdateChannel(settings);
autoUpdater.removeAllListeners();
@@ -50,10 +73,12 @@ export class Updater {
autoUpdater.on("update-available", () => {
console.log("update-available; downloading...");
this.status = "downloading";
});
autoUpdater.on("update-not-available", () => {
console.log("update-not-available");
this.status = "up-to-date";
});
autoUpdater.on("update-downloaded", (event) => {
@@ -180,6 +205,9 @@ ipcMain.on("install-app-update", () => fireAndForget(() => updater?.promptToInst
ipcMain.on("get-app-update-status", (event) => {
event.returnValue = updater?.status;
});
ipcMain.on("get-updater-channel", (event) => {
event.returnValue = isDev() ? "dev" : (autoUpdater.channel ?? "latest");
});
let autoUpdateLock = false;
+37 -36
View File
@@ -1,5 +1,5 @@
/* Copyright 2024, Command Line Inc. */
/* SPDX-License-Identifier: Apache-2.0 */
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.button {
// override default button appearance
@@ -26,41 +26,41 @@
&.solid {
&.green {
color: #000000;
color: var(--button-text-color);
background-color: var(--accent-color);
border: 1px solid #29f200;
border: 1px solid var(--button-green-border-color);
&:hover {
color: #000000;
background-color: #29f200;
color: var(--button-text-color);
background-color: var(--button-green-border-color);
}
}
&.grey {
background-color: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
background-color: var(--button-grey-bg);
border: 1px solid var(--button-grey-bg);
color: var(--main-text-color);
&:hover {
color: var(--main-text-color);
background-color: rgba(255, 255, 255, 0.09);
background-color: var(--button-grey-hover-bg);
}
}
&.red {
background-color: #cc0000;
border: 1px solid #fc3131;
background-color: var(--button-red-bg);
border: 1px solid var(--button-red-border-color);
color: var(--main-text-color);
&:hover {
background-color: #f93939;
background-color: var(--button-red-hover-bg);
}
}
&.yellow {
color: #000000;
background-color: #c4a000;
border: 1px solid #fce94f;
color: var(--button-text-color);
background-color: var(--button-yellow-bg);
border: 1px solid var(--button-yellow-hover-bg);
&:hover {
color: #000000;
background-color: #fce94f;
color: var(--button-text-color);
background-color: var(--button-yellow-hover-bg);
}
}
}
@@ -71,14 +71,14 @@
color: var(--accent-color);
border: 1px solid var(--accent-color);
&:hover {
color: #29f200;
border: 1px solid #29f200;
color: var(--button-green-border-color);
border: 1px solid var(--button-green-border-color);
}
}
&.grey {
border: 1px solid rgba(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.6);
border: 1px solid var(--button-grey-outlined-color);
color: var(--button-grey-outlined-color);
&:hover {
color: var(--main-text-color);
border: 1px solid var(--main-text-color);
@@ -86,20 +86,20 @@
}
&.red {
border: 1px solid #cc0000;
color: #cc0000;
border: 1px solid var(--button-red-bg);
color: var(--button-red-bg);
&:hover {
color: #ff3c3c;
border: 1px solid #ff3c3c;
color: var(--button-red-outlined-color);
border: 1px solid var(--button-red-outlined-color);
}
}
&.yellow {
color: #c4a000;
border: 1px solid #c4a000;
color: var(--button-yellow-bg);
border: 1px solid var(--button-yellow-bg);
&:hover {
color: #fce94f;
border: 1px solid #fce94f;
color: var(--button-yellow-hover-bg);
border: 1px solid var(--button-yellow-hover-bg);
}
}
}
@@ -115,13 +115,13 @@
border: none;
color: var(--accent-color);
&:hover {
color: #29f200;
color: var(--button-green-border-color);
}
}
&.grey {
border: none;
color: rgba(255, 255, 255, 0.6);
color: var(--button-grey-outlined-color);
&:hover {
color: var(--main-text-color);
}
@@ -129,24 +129,25 @@
&.red {
border: none;
color: #cc0000;
color: var(--button-red-bg);
&:hover {
color: #fc3131;
color: var(--button-red-border-color);
}
}
&.yellow {
border: none;
color: #c4a000;
color: var(--button-yellow-bg);
&:hover {
color: #fce94f;
color: var(--button-yellow-hover-bg);
}
}
}
&.disabled {
&:disabled {
cursor: default;
opacity: 0.5;
pointer-events: none;
}
&:focus,
+1 -3
View File
@@ -35,9 +35,7 @@ const Button = memo(
<button
ref={btnRef}
tabIndex={disabled ? -1 : 0}
className={clsx("button", finalClassName, {
disabled,
})}
className={clsx("button", finalClassName)}
disabled={disabled}
{...props}
>
-15
View File
@@ -3,19 +3,4 @@
&.link-button {
text-decoration: none;
.button-inner {
display: flex;
border-radius: 6px;
padding: 8px 12px;
background-color: var(--button-grey-bg);
border: 1px solid var(--button-grey-border-color);
color: var(--button-grey-text-color);
i {
margin-right: 4px;
}
&:hover {
color: var(--button-grey-text-color);
}
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ interface LinkButtonProps {
const LinkButton = ({ leftIcon, rightIcon, children, className, ...rest }: LinkButtonProps) => {
return (
<a {...rest} className={clsx("link-button", className)}>
<a {...rest} className={clsx("button grey solid link-button", className)}>
<span className="button-inner">
{leftIcon && <span className="icon-left">{leftIcon}</span>}
{children}
+9 -5
View File
@@ -2,14 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
.about-modal {
width: 445px;
padding-bottom: 34px;
.modal-content {
.content-wrapper .modal-content {
margin-bottom: 0;
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0px 0px;
gap: 24px;
.section-wrapper {
@@ -43,15 +43,19 @@
}
}
.section:nth-child(3) {
.section.links {
display: flex;
align-items: flex-start;
gap: 7px;
gap: 10px;
.wave-button-link {
.button {
display: flex;
align-items: center;
.icon-left {
margin-right: 5px;
}
i {
font-size: 16px;
}
+4 -1
View File
@@ -16,6 +16,7 @@ interface AboutModalProps {}
const AboutModal = ({}: AboutModalProps) => {
const currentDate = new Date();
const [details] = useState(() => getApi().getAboutModalDetails());
const [updaterChannel] = useState(() => getApi().getUpdaterChannel());
return (
<Modal className="about-modal" onClose={() => modalsModel.popModal()}>
@@ -32,8 +33,10 @@ const AboutModal = ({}: AboutModalProps) => {
<div className="section text-standard">
Client Version {details.version} ({isDev() ? "dev-" : ""}
{details.buildTime})
<br />
Update Channel: {updaterChannel}
</div>
<div className="section">
<div className="section links">
<LinkButton
className="secondary solid"
href="https://github.com/wavetermdev/waveterm"
+23 -21
View File
@@ -1,20 +1,12 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { type WebSocket, newWebSocket } from "@/util/wsutil";
import debug from "debug";
import { sprintf } from "sprintf-js";
import type { WebSocket as ElectronWebSocketType } from "ws";
let ElectronWebSocket: typeof ElectronWebSocketType;
const AuthKeyHeader = "X-AuthKey";
if (typeof window === "undefined") {
try {
const WebSocket = require("ws") as typeof ElectronWebSocketType;
ElectronWebSocket = WebSocket;
} catch (e) {}
}
const dlog = debug("wave:ws");
const WarnWebSocketSendSize = 1024 * 1024; // 1MB
@@ -34,8 +26,12 @@ function removeWSReconnectHandler(handler: () => void) {
type WSEventCallback = (arg0: WSEventType) => void;
type ElectronOverrideOpts = {
authKey: string;
};
class WSControl {
wsConn: WebSocket | ElectronWebSocketType;
wsConn: WebSocket;
open: boolean;
opening: boolean = false;
reconnectTimes: number = 0;
@@ -47,14 +43,19 @@ class WSControl {
wsLog: string[] = [];
baseHostPort: string;
lastReconnectTime: number = 0;
authKey: string = null; // used only by electron
eoOpts: ElectronOverrideOpts;
constructor(baseHostPort: string, windowId: string, messageCallback: WSEventCallback, authKey?: string) {
constructor(
baseHostPort: string,
windowId: string,
messageCallback: WSEventCallback,
electronOverrideOpts?: ElectronOverrideOpts
) {
this.baseHostPort = baseHostPort;
this.messageCallback = messageCallback;
this.windowId = windowId;
this.open = false;
this.authKey = authKey;
this.eoOpts = electronOverrideOpts;
setInterval(this.sendPing.bind(this), 5000);
}
@@ -65,13 +66,14 @@ class WSControl {
this.lastReconnectTime = Date.now();
dlog("try reconnect:", desc);
this.opening = true;
if (ElectronWebSocket) {
this.wsConn = new ElectronWebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId, {
headers: { [AuthKeyHeader]: this.authKey },
});
} else {
this.wsConn = new WebSocket(this.baseHostPort + "/ws?windowid=" + this.windowId);
}
this.wsConn = newWebSocket(
this.baseHostPort + "/ws?windowid=" + this.windowId,
this.eoOpts
? {
[AuthKeyHeader]: this.eoOpts.authKey,
}
: null
);
this.wsConn.onopen = this.onopen.bind(this);
this.wsConn.onmessage = this.onmessage.bind(this);
this.wsConn.onclose = this.onclose.bind(this);
@@ -207,4 +209,4 @@ class WSControl {
}
}
export { addWSReconnectHandler, removeWSReconnectHandler, WSControl };
export { WSControl, addWSReconnectHandler, removeWSReconnectHandler, type ElectronOverrideOpts };
+3 -3
View File
@@ -5,7 +5,7 @@ import { wpsReconnectHandler } from "@/app/store/wps";
import { WshClient } from "@/app/store/wshclient";
import { makeWindowRouteId, WshRouter } from "@/app/store/wshrouter";
import { getWSServerEndpoint } from "@/util/endpoints";
import { addWSReconnectHandler, WSControl } from "./ws";
import { addWSReconnectHandler, ElectronOverrideOpts, WSControl } from "./ws";
let globalWS: WSControl;
let DefaultRouter: WshRouter;
@@ -114,12 +114,12 @@ if (globalThis.window != null) {
globalThis["consumeGenerator"] = consumeGenerator;
}
function initElectronWshrpc(electronClient: WshClient, authKey: string) {
function initElectronWshrpc(electronClient: WshClient, eoOpts: ElectronOverrideOpts) {
DefaultRouter = new WshRouter(new UpstreamWshRpcProxy());
const handleFn = (event: WSEventType) => {
DefaultRouter.recvRpcMessage(event.data);
};
globalWS = new WSControl(getWSServerEndpoint(), "electron", handleFn, authKey);
globalWS = new WSControl(getWSServerEndpoint(), "electron", handleFn, eoOpts);
globalWS.connectNow("connectWshrpc");
DefaultRouter.registerRoute(electronClient.routeId, electronClient);
addWSReconnectHandler(() => {

Some files were not shown because too many files have changed in this diff Show More