mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Wait for shell to bring up prompt before sending commands (#3337)
This commit is contained in:
parent
aeae2dcf98
commit
27f2bd7181
@ -19,11 +19,13 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { stringify } from "querystring";
|
|
||||||
import { boundMethod, base64, EventEmitter } from "../utils";
|
import { boundMethod, base64, EventEmitter } from "../utils";
|
||||||
import { WebSocketApi } from "./websocket-api";
|
import { WebSocketApi } from "./websocket-api";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
import { isDevelopment } from "../../common/vars";
|
import { isDevelopment } from "../../common/vars";
|
||||||
|
import url from "url";
|
||||||
|
import { makeObservable, observable } from "mobx";
|
||||||
|
import type { ParsedUrlQueryInput } from "querystring";
|
||||||
|
|
||||||
export enum TerminalChannels {
|
export enum TerminalChannels {
|
||||||
STDIN = 0,
|
STDIN = 0,
|
||||||
@ -55,7 +57,9 @@ export class TerminalApi extends WebSocketApi {
|
|||||||
protected size: { Width: number; Height: number };
|
protected size: { Width: number; Height: number };
|
||||||
|
|
||||||
public onReady = new EventEmitter<[]>();
|
public onReady = new EventEmitter<[]>();
|
||||||
public isReady = false;
|
@observable public isReady = false;
|
||||||
|
@observable public shellRunCommandsFinished = false;
|
||||||
|
public readonly url: string;
|
||||||
|
|
||||||
constructor(protected options: TerminalApiQuery) {
|
constructor(protected options: TerminalApiQuery) {
|
||||||
super({
|
super({
|
||||||
@ -63,34 +67,33 @@ export class TerminalApi extends WebSocketApi {
|
|||||||
flushOnOpen: false,
|
flushOnOpen: false,
|
||||||
pingIntervalSeconds: 30,
|
pingIntervalSeconds: 30,
|
||||||
});
|
});
|
||||||
}
|
makeObservable(this);
|
||||||
|
|
||||||
async getUrl() {
|
const { hostname, protocol, port } = location;
|
||||||
let { port } = location;
|
const query: ParsedUrlQueryInput = {
|
||||||
const { hostname, protocol } = location;
|
id: options.id,
|
||||||
const { id, node } = this.options;
|
};
|
||||||
const wss = `ws${protocol === "https:" ? "s" : ""}://`;
|
|
||||||
const query: TerminalApiQuery = { id };
|
|
||||||
|
|
||||||
if (port) {
|
if (options.node) {
|
||||||
port = `:${port}`;
|
query.node = options.node;
|
||||||
|
query.type = options.type || "node";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
this.url = url.format({
|
||||||
query.node = node;
|
protocol: protocol.includes("https") ? "wss" : "ws",
|
||||||
query.type = "node";
|
hostname,
|
||||||
}
|
port,
|
||||||
|
pathname: "/api",
|
||||||
return `${wss}${hostname}${port}/api?${stringify(query)}`;
|
query,
|
||||||
|
slashes: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
connect() {
|
||||||
const apiUrl = await this.getUrl();
|
|
||||||
|
|
||||||
this.emitStatus("Connecting ...");
|
this.emitStatus("Connecting ...");
|
||||||
this.onData.addListener(this._onReady, { prepend: true });
|
this.onData.addListener(this._onReady, { prepend: true });
|
||||||
|
this.onData.addListener(this._onShellRunCommandsFinished);
|
||||||
return super.connect(apiUrl);
|
super.connect(this.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
@ -106,6 +109,24 @@ export class TerminalApi extends WebSocketApi {
|
|||||||
this.onReady.removeAllListeners();
|
this.onReady.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onShellRunCommandsFinished = (data: string) => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a heuistic for ditermining when a shell has finished executing
|
||||||
|
* its own rc file (or RunCommands file) such as `.bashrc` or `.zshrc`.
|
||||||
|
*
|
||||||
|
* This heuistic assumes that the prompt line of a terminal is a single line
|
||||||
|
* and ends with a whitespace character.
|
||||||
|
*/
|
||||||
|
if (data.match(/\r?\n/) === null && data.match(/\s$/)) {
|
||||||
|
this.shellRunCommandsFinished = true;
|
||||||
|
this.onData.removeListener(this._onShellRunCommandsFinished);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
protected _onReady(data: string) {
|
protected _onReady(data: string) {
|
||||||
if (!data) return true;
|
if (!data) return true;
|
||||||
|
|||||||
@ -19,12 +19,13 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { autorun, observable } from "mobx";
|
import { autorun, observable, when } from "mobx";
|
||||||
import { autoBind, Singleton } from "../../utils";
|
import { autoBind, noop, Singleton } from "../../utils";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
import { TerminalApi } from "../../api/terminal-api";
|
import { TerminalApi } from "../../api/terminal-api";
|
||||||
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
import { dockStore, DockTab, DockTabCreateSpecific, TabId, TabKind } from "./dock.store";
|
||||||
import { WebSocketApiState } from "../../api/websocket-api";
|
import { WebSocketApiState } from "../../api/websocket-api";
|
||||||
|
import { Notifications } from "../notifications";
|
||||||
|
|
||||||
export interface ITerminalTab extends DockTab {
|
export interface ITerminalTab extends DockTab {
|
||||||
node?: string; // activate node shell mode
|
node?: string; // activate node shell mode
|
||||||
@ -64,7 +65,7 @@ export class TerminalStore extends Singleton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(tabId: TabId) {
|
connect(tabId: TabId) {
|
||||||
if (this.isConnected(tabId)) {
|
if (this.isConnected(tabId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -104,18 +105,36 @@ export class TerminalStore extends Singleton {
|
|||||||
return this.connections.get(tabId)?.readyState === WebSocketApiState.CLOSED;
|
return this.connections.get(tabId)?.readyState === WebSocketApiState.CLOSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) {
|
async sendCommand(command: string, options: { enter?: boolean; newTab?: boolean; tabId?: TabId } = {}) {
|
||||||
const { enter, newTab, tabId } = options;
|
const { enter, newTab, tabId } = options;
|
||||||
const { selectTab, getTabById } = dockStore;
|
|
||||||
const tab = tabId && getTabById(tabId);
|
|
||||||
|
|
||||||
if (tab) selectTab(tabId);
|
if (tabId) {
|
||||||
if (newTab) createTerminalTab();
|
dockStore.selectTab(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTab) {
|
||||||
|
const tab = createTerminalTab();
|
||||||
|
|
||||||
|
await when(() => this.connections.has(tab.id));
|
||||||
|
|
||||||
|
const rcIsFinished = when(() => this.connections.get(tab.id).shellRunCommandsFinished);
|
||||||
|
const notifyVeryLong = setTimeout(() => {
|
||||||
|
rcIsFinished.cancel();
|
||||||
|
Notifications.info("Terminal shell is taking a long time to complete startup. Please check your .rc file. Bypassing shell completion check.", {
|
||||||
|
timeout: 4_000,
|
||||||
|
});
|
||||||
|
}, 10_000);
|
||||||
|
|
||||||
|
await rcIsFinished.catch(noop);
|
||||||
|
clearTimeout(notifyVeryLong);
|
||||||
|
}
|
||||||
|
|
||||||
const terminalApi = this.connections.get(dockStore.selectedTabId);
|
const terminalApi = this.connections.get(dockStore.selectedTabId);
|
||||||
|
|
||||||
if (terminalApi) {
|
if (terminalApi) {
|
||||||
terminalApi.sendCommand(command + (enter ? "\r" : ""));
|
terminalApi.sendCommand(command + (enter ? "\r" : ""));
|
||||||
|
} else {
|
||||||
|
console.warn("The selected tab is does not have a connection. Cannot send command.", { tabId: dockStore.selectedTabId, command });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user