diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index c03e04a94..9c801c6ee 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -51,6 +51,7 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) { setupEsbuildTailwind(build, dev) }, }}, + JSXMode: esbuild.JSXModeAutomatic, }, nil } diff --git a/cmd/tsconnect/index.html b/cmd/tsconnect/index.html index 0d78a1cec..3db45fdef 100644 --- a/cmd/tsconnect/index.html +++ b/cmd/tsconnect/index.html @@ -8,37 +8,13 @@ +

Tailscale Connect

-
Loading…
+
Loading…
-
- - -
diff --git a/cmd/tsconnect/package.json b/cmd/tsconnect/package.json index 7f09527ff..06bee1e13 100644 --- a/cmd/tsconnect/package.json +++ b/cmd/tsconnect/package.json @@ -5,6 +5,7 @@ "devDependencies": { "@types/golang-wasm-exec": "^1.15.0", "@types/qrcode": "^1.4.2", + "preact": "^10.10.0", "qrcode": "^1.5.0", "tailwindcss": "^3.1.6", "typescript": "^4.7.4", diff --git a/cmd/tsconnect/src/app.tsx b/cmd/tsconnect/src/app.tsx new file mode 100644 index 000000000..27c44d3c7 --- /dev/null +++ b/cmd/tsconnect/src/app.tsx @@ -0,0 +1,124 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { render, Component } from "preact" +import { IPNState } from "./wasm_js" +import { URLDisplay } from "./url-display" +import { Header } from "./header" +import { GoPanicDisplay } from "./go-panic-display" +import { SSH } from "./ssh" + +type AppState = { + ipn?: IPN + ipnState: IPNState + netMap?: IPNNetMap + browseToURL?: string + goPanicError?: string +} + +class App extends Component<{}, AppState> { + state: AppState = { ipnState: IPNState.NoState } + #goPanicTimeout?: number + + render() { + const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state + + let goPanicDisplay + if (goPanicError) { + goPanicDisplay = ( + + ) + } + + let urlDisplay + if (browseToURL) { + urlDisplay = + } + + let machineAuthInstructions + if (ipnState === IPNState.NeedsMachineAuth) { + machineAuthInstructions = ( +
+ An administrator needs to authorize this device. +
+ ) + } + + let ssh + if (ipn && ipnState === IPNState.Running && netMap) { + ssh = + } + + return ( + <> +
+ {goPanicDisplay} +
+ {urlDisplay} + {machineAuthInstructions} + {ssh} +
+ + ) + } + + runWithIPN(ipn: IPN) { + this.setState({ ipn }, () => { + ipn.run({ + notifyState: this.handleIPNState, + notifyNetMap: this.handleNetMap, + notifyBrowseToURL: this.handleBrowseToURL, + notifyPanicRecover: this.handleGoPanic, + }) + }) + } + + handleIPNState = (state: IPNState) => { + const { ipn } = this.state + this.setState({ ipnState: state }) + if (state == IPNState.NeedsLogin) { + ipn?.login() + } else if ([IPNState.Running, IPNState.NeedsMachineAuth].includes(state)) { + this.setState({ browseToURL: undefined }) + } + } + + handleNetMap = (netMapStr: string) => { + const netMap = JSON.parse(netMapStr) as IPNNetMap + if (DEBUG) { + console.log("Received net map: " + JSON.stringify(netMap, null, 2)) + } + this.setState({ netMap }) + } + + handleBrowseToURL = (url: string) => { + this.setState({ browseToURL: url }) + } + + handleGoPanic = (error: string) => { + if (DEBUG) { + console.error("Go panic", error) + } + this.setState({ goPanicError: error }) + if (this.#goPanicTimeout) { + window.clearTimeout(this.#goPanicTimeout) + } + this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000) + } + + clearGoPanic = () => { + window.clearTimeout(this.#goPanicTimeout) + this.#goPanicTimeout = undefined + this.setState({ goPanicError: undefined }) + } +} + +export function renderApp(): Promise { + return new Promise((resolve) => { + render( + (app ? resolve(app) : undefined)} />, + document.body + ) + }) +} diff --git a/cmd/tsconnect/src/go-panic-display.tsx b/cmd/tsconnect/src/go-panic-display.tsx new file mode 100644 index 000000000..d7c0b808f --- /dev/null +++ b/cmd/tsconnect/src/go-panic-display.tsx @@ -0,0 +1,21 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +export function GoPanicDisplay({ + error, + dismiss, +}: { + error: string + dismiss: () => void +}) { + return ( +
+ Tailscale has encountered an error. +
Click to reload
+
+ ) +} diff --git a/cmd/tsconnect/src/header.tsx b/cmd/tsconnect/src/header.tsx new file mode 100644 index 000000000..ff08adef9 --- /dev/null +++ b/cmd/tsconnect/src/header.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { IPNState } from "./wasm_js" + +export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { + const stateText = STATE_LABELS[state] + + let logoutButton + if (state === IPNState.Running) { + logoutButton = ( + + ) + } + return ( +
+
+

Tailscale Connect

+
{stateText}
+ {logoutButton} +
+
+ ) +} + +const STATE_LABELS = { + [IPNState.NoState]: "Initializing…", + [IPNState.InUseOtherUser]: "In-use by another user", + [IPNState.NeedsLogin]: "Needs login", + [IPNState.NeedsMachineAuth]: "Needs authorization", + [IPNState.Stopped]: "Stopped", + [IPNState.Starting]: "Starting…", + [IPNState.Running]: "Running", +} as const diff --git a/cmd/tsconnect/src/index.css b/cmd/tsconnect/src/index.css index c0ad0b037..24dd0cb64 100644 --- a/cmd/tsconnect/src/index.css +++ b/cmd/tsconnect/src/index.css @@ -73,7 +73,3 @@ background-color: currentColor; clip-path: polygon(100% 0%, 0 0%, 50% 100%); } - -body.ssh-active #ssh-form { - @apply hidden; -} diff --git a/cmd/tsconnect/src/index.ts b/cmd/tsconnect/src/index.ts index 8716a69bb..198d4d5ac 100644 --- a/cmd/tsconnect/src/index.ts +++ b/cmd/tsconnect/src/index.ts @@ -4,55 +4,26 @@ import "./wasm_exec" import wasmUrl from "./main.wasm" -import { notifyState, notifyNetMap, notifyBrowseToURL } from "./notifier" import { sessionStateStorage } from "./js-state-store" +import { renderApp } from "./app" -const go = new Go() -WebAssembly.instantiateStreaming( - fetch(`./dist/${wasmUrl}`), - go.importObject -).then((result) => { +async function main() { + const app = await renderApp() + const go = new Go() + const wasmInstance = await WebAssembly.instantiateStreaming( + fetch(`./dist/${wasmUrl}`), + go.importObject + ) // The Go process should never exit, if it does then it's an unhandled panic. - go.run(result.instance).then(() => handleGoPanic()) + go.run(wasmInstance.instance).then(() => + app.handleGoPanic("Unexpected shutdown") + ) const ipn = newIPN({ // Persist IPN state in sessionStorage in development, so that we don't need // to re-authorize every time we reload the page. stateStorage: DEBUG ? sessionStateStorage : undefined, }) - ipn.run({ - notifyState: notifyState.bind(null, ipn), - notifyNetMap: notifyNetMap.bind(null, ipn), - notifyBrowseToURL: notifyBrowseToURL.bind(null, ipn), - notifyPanicRecover: handleGoPanic, - }) -}) - -function handleGoPanic(err?: string) { - if (DEBUG && err) { - console.error("Go panic", err) - } - if (panicNode) { - panicNode.remove() - } - panicNode = document.createElement("div") - panicNode.className = - "rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer" - panicNode.textContent = "Tailscale has encountered an error." - const panicDetailNode = document.createElement("div") - panicDetailNode.className = "text-sm font-normal" - panicDetailNode.textContent = "Click to reload" - panicNode.appendChild(panicDetailNode) - panicNode.addEventListener("click", () => location.reload(), { - once: true, - }) - document.body.appendChild(panicNode) - setTimeout(() => { - panicNode!.remove() - }, 10000) + app.runWithIPN(ipn) } -let panicNode: HTMLDivElement | undefined - -export function getContentNode(): HTMLDivElement { - return document.querySelector("#content") as HTMLDivElement -} +main() diff --git a/cmd/tsconnect/src/login.ts b/cmd/tsconnect/src/login.ts deleted file mode 100644 index 5431cab44..000000000 --- a/cmd/tsconnect/src/login.ts +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import * as qrcode from "qrcode" -import { getContentNode } from "./index" - -export async function showLoginURL(url: string) { - if (loginNode) { - loginNode.remove() - } - loginNode = document.createElement("div") - loginNode.className = "flex flex-col items-center justify-items-center" - const linkNode = document.createElement("a") - linkNode.className = "link" - linkNode.href = url - linkNode.target = "_blank" - loginNode.appendChild(linkNode) - - try { - const dataURL = await qrcode.toDataURL(url, { width: 512 }) - const imageNode = document.createElement("img") - imageNode.className = "mx-auto" - imageNode.src = dataURL - imageNode.width = 256 - imageNode.height = 256 - linkNode.appendChild(imageNode) - } catch (err) { - console.error("Could not generate QR code:", err) - } - - linkNode.appendChild(document.createTextNode(url)) - - getContentNode().appendChild(loginNode) -} - -export function hideLoginURL() { - if (!loginNode) { - return - } - loginNode.remove() - loginNode = undefined -} - -let loginNode: HTMLDivElement | undefined - -export function showLogoutButton(ipn: IPN) { - if (logoutButtonNode) { - logoutButtonNode.remove() - } - logoutButtonNode = document.createElement("button") - logoutButtonNode.className = - "button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold" - logoutButtonNode.textContent = "Logout" - logoutButtonNode.addEventListener( - "click", - () => { - ipn.logout() - }, - { once: true } - ) - const headerNode = document.getElementsByTagName("header")[0]! - headerNode.appendChild(logoutButtonNode) -} - -export function hideLogoutButton() { - if (!logoutButtonNode) { - return - } - logoutButtonNode.remove() - logoutButtonNode = undefined -} - -let logoutButtonNode: HTMLButtonElement | undefined diff --git a/cmd/tsconnect/src/notifier.ts b/cmd/tsconnect/src/notifier.ts deleted file mode 100644 index a5ce3ffca..000000000 --- a/cmd/tsconnect/src/notifier.ts +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import { - showLoginURL, - hideLoginURL, - showLogoutButton, - hideLogoutButton, -} from "./login" -import { showSSHForm, hideSSHForm } from "./ssh" -import { IPNState } from "./wasm_js" - -/** - * @fileoverview Notification callback functions (bridged from ipn.Notify) - */ - -export function notifyState(ipn: IPN, state: IPNState) { - let stateLabel - switch (state) { - case IPNState.NoState: - stateLabel = "Initializing…" - break - case IPNState.InUseOtherUser: - stateLabel = "In-use by another user" - break - case IPNState.NeedsLogin: - stateLabel = "Needs Login" - hideLogoutButton() - hideSSHForm() - ipn.login() - break - case IPNState.NeedsMachineAuth: - stateLabel = "Needs authorization" - break - case IPNState.Stopped: - stateLabel = "Stopped" - hideLogoutButton() - hideSSHForm() - break - case IPNState.Starting: - stateLabel = "Starting…" - break - case IPNState.Running: - stateLabel = "Running" - hideLoginURL() - showLogoutButton(ipn) - break - } - const stateNode = document.querySelector("#state") as HTMLDivElement - stateNode.textContent = stateLabel ?? "" -} - -export function notifyNetMap(ipn: IPN, netMapStr: string) { - const netMap = JSON.parse(netMapStr) as IPNNetMap - if (DEBUG) { - console.log("Received net map: " + JSON.stringify(netMap, null, 2)) - } - - showSSHForm(netMap.peers, ipn) -} - -export function notifyBrowseToURL(ipn: IPN, url: string) { - showLoginURL(url) -} diff --git a/cmd/tsconnect/src/ssh.ts b/cmd/tsconnect/src/ssh.ts deleted file mode 100644 index 1bdaf9307..000000000 --- a/cmd/tsconnect/src/ssh.ts +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import { Terminal } from "xterm" -import { FitAddon } from "xterm-addon-fit" -import { getContentNode } from "./index" - -export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) { - const formNode = document.querySelector("#ssh-form") as HTMLDivElement - const noSSHNode = document.querySelector("#no-ssh") as HTMLDivElement - - const sshPeers = peers.filter( - (p) => p.tailscaleSSHEnabled && p.online !== false - ) - if (sshPeers.length == 0) { - formNode.classList.add("hidden") - noSSHNode.classList.remove("hidden") - return - } - sshPeers.sort((a, b) => a.name.localeCompare(b.name)) - - const selectNode = formNode.querySelector("select")! - selectNode.innerHTML = "" - for (const p of sshPeers) { - const option = document.createElement("option") - option.textContent = p.name.split(".")[0] - option.value = p.name - selectNode.appendChild(option) - } - - const usernameNode = formNode.querySelector(".username") as HTMLInputElement - formNode.onsubmit = (e) => { - e.preventDefault() - const hostname = selectNode.value - ssh(hostname, usernameNode.value, ipn) - } - - noSSHNode.classList.add("hidden") - formNode.classList.remove("hidden") -} - -export function hideSSHForm() { - const formNode = document.querySelector("#ssh-form") as HTMLDivElement - formNode.classList.add("hidden") -} - -function ssh(hostname: string, username: string, ipn: IPN) { - document.body.classList.add("ssh-active") - const termContainerNode = document.createElement("div") - termContainerNode.className = "flex-grow bg-black p-2 overflow-hidden" - getContentNode().appendChild(termContainerNode) - - const term = new Terminal({ - cursorBlink: true, - }) - const fitAddon = new FitAddon() - term.loadAddon(fitAddon) - term.open(termContainerNode) - fitAddon.fit() - - let onDataHook: ((data: string) => void) | undefined - term.onData((e) => { - onDataHook?.(e) - }) - - term.focus() - - const sshSession = ipn.ssh(hostname, username, { - writeFn: (input) => term.write(input), - setReadFn: (hook) => (onDataHook = hook), - rows: term.rows, - cols: term.cols, - onDone: () => { - resizeObserver.disconnect() - term.dispose() - termContainerNode.remove() - document.body.classList.remove("ssh-active") - window.removeEventListener("beforeunload", beforeUnloadListener) - }, - }) - - // Make terminal and SSH session track the size of the containing DOM node. - const resizeObserver = new ResizeObserver((entries) => { - fitAddon.fit() - }) - resizeObserver.observe(termContainerNode) - term.onResize(({ rows, cols }) => { - sshSession.resize(rows, cols) - }) - - // Close the session if the user closes the window without an explicit - // exit. - const beforeUnloadListener = () => { - sshSession.close() - } - window.addEventListener("beforeunload", beforeUnloadListener) -} diff --git a/cmd/tsconnect/src/ssh.tsx b/cmd/tsconnect/src/ssh.tsx new file mode 100644 index 000000000..91ebee04f --- /dev/null +++ b/cmd/tsconnect/src/ssh.tsx @@ -0,0 +1,156 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { useState, useCallback } from "preact/hooks" +import { Terminal } from "xterm" +import { FitAddon } from "xterm-addon-fit" + +type SSHSessionDef = { + username: string + hostname: string +} + +export function SSH({ netMap, ipn }: { netMap: IPNNetMap; ipn: IPN }) { + const [sshSessionDef, setSSHSessionDef] = useState(null) + const clearSSHSessionDef = useCallback(() => setSSHSessionDef(null), []) + if (sshSessionDef) { + return ( + + ) + } + const sshPeers = netMap.peers.filter( + (p) => p.tailscaleSSHEnabled && p.online !== false + ) + + if (sshPeers.length == 0) { + return + } + + return +} + +function SSHSession({ + def, + ipn, + onDone, +}: { + def: SSHSessionDef + ipn: IPN + onDone: () => void +}) { + return ( +
{ + if (node) { + // Run the SSH session aysnchronously, so that the React render + // loop is complete (otherwise the SSH form may still be visible, + // which affects the size of the terminal, leading to a spurious + // initial resize). + setTimeout(() => runSSHSession(node, def, ipn, onDone), 0) + } + }} + /> + ) +} + +function runSSHSession( + termContainerNode: HTMLDivElement, + def: SSHSessionDef, + ipn: IPN, + onDone: () => void +) { + const term = new Terminal({ + cursorBlink: true, + }) + const fitAddon = new FitAddon() + term.loadAddon(fitAddon) + term.open(termContainerNode) + fitAddon.fit() + + let onDataHook: ((data: string) => void) | undefined + term.onData((e) => { + onDataHook?.(e) + }) + + term.focus() + + const sshSession = ipn.ssh(def.hostname, def.username, { + writeFn: (input) => term.write(input), + setReadFn: (hook) => (onDataHook = hook), + rows: term.rows, + cols: term.cols, + onDone: () => { + resizeObserver.disconnect() + term.dispose() + window.removeEventListener("beforeunload", handleBeforeUnload) + onDone() + }, + }) + + // Make terminal and SSH session track the size of the containing DOM node. + const resizeObserver = new ResizeObserver(() => fitAddon.fit()) + resizeObserver.observe(termContainerNode) + term.onResize(({ rows, cols }) => sshSession.resize(rows, cols)) + + // Close the session if the user closes the window without an explicit + // exit. + const handleBeforeUnload = () => sshSession.close() + window.addEventListener("beforeunload", handleBeforeUnload) +} + +function NoSSHPeers() { + return ( +
+ None of your machines have + + Tailscale SSH + + enabled. Give it a try! +
+ ) +} + +function SSHForm({ + sshPeers, + onSubmit, +}: { + sshPeers: IPNNetMapPeerNode[] + onSubmit: (def: SSHSessionDef) => void +}) { + sshPeers = sshPeers.slice().sort((a, b) => a.name.localeCompare(b.name)) + const [username, setUsername] = useState("") + const [hostname, setHostname] = useState(sshPeers[0].name) + return ( +
{ + e.preventDefault() + onSubmit({ username, hostname }) + }} + > + setUsername(e.currentTarget.value)} + /> +
+ +
+ +
+ ) +} diff --git a/cmd/tsconnect/src/url-display.tsx b/cmd/tsconnect/src/url-display.tsx new file mode 100644 index 000000000..47ee1fe4c --- /dev/null +++ b/cmd/tsconnect/src/url-display.tsx @@ -0,0 +1,32 @@ +// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import { useState } from "preact/hooks" +import * as qrcode from "qrcode" + +export function URLDisplay({ url }: { url: string }) { + const [dataURL, setDataURL] = useState("") + qrcode.toDataURL(url, { width: 512 }, (err, dataURL) => { + if (err) { + console.error("Error generating QR code", err) + } else { + setDataURL(dataURL) + } + }) + + return ( + + ) +} diff --git a/cmd/tsconnect/tailwind.config.js b/cmd/tsconnect/tailwind.config.js index dcd5f5878..31823000b 100644 --- a/cmd/tsconnect/tailwind.config.js +++ b/cmd/tsconnect/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./index.html", "./src/**/*.ts"], + content: ["./index.html", "./src/**/*.ts", "./src/**/*.tsx"], theme: { extend: {}, }, diff --git a/cmd/tsconnect/tsconfig.json b/cmd/tsconnect/tsconfig.json index 9a3ccb290..52c25c727 100644 --- a/cmd/tsconnect/tsconfig.json +++ b/cmd/tsconnect/tsconfig.json @@ -6,7 +6,9 @@ "isolatedModules": true, "strict": true, "forceConsistentCasingInFileNames": true, - "sourceMap": true + "sourceMap": true, + "jsx": "react-jsx", + "jsxImportSource": "preact" }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go index 121f77ae0..750270a46 100644 --- a/cmd/tsconnect/wasm/wasm_js.go +++ b/cmd/tsconnect/wasm/wasm_js.go @@ -203,7 +203,7 @@ func (i *jsIPN) run(jsCallbacks js.Value) { if n.State != nil { notifyState(*n.State) } - if nm := n.NetMap; nm != nil && i.lb.State() == ipn.Running { + if nm := n.NetMap; nm != nil { jsNetMap := jsNetMap{ Self: jsNetMapSelfNode{ jsNetMapNode: jsNetMapNode{ diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock index 81c19436b..d9dad03fb 100644 --- a/cmd/tsconnect/yarn.lock +++ b/cmd/tsconnect/yarn.lock @@ -443,6 +443,11 @@ postcss@^8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" +preact@^10.10.0: + version "10.10.0" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.10.0.tgz#7434750a24b59dae1957d95dc0aa47a4a8e9a180" + integrity sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ== + qrcode@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" diff --git a/go.mod b/go.mod index 5257e85aa..706662ab5 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/creack/pty v1.1.17 github.com/dave/jennifer v1.4.1 - github.com/evanw/esbuild v0.14.39 + github.com/evanw/esbuild v0.14.53 github.com/frankban/quicktest v1.14.0 github.com/fxamacker/cbor/v2 v2.4.0 github.com/go-ole/go-ole v1.2.6 @@ -59,7 +59,7 @@ require ( golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/net v0.0.0-20220607020251-c690dde0001d golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f - golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.11 diff --git a/go.sum b/go.sum index 5938ecbc0..7849f6702 100644 --- a/go.sum +++ b/go.sum @@ -278,8 +278,8 @@ github.com/esimonov/ifshort v1.0.3 h1:JD6x035opqGec5fZ0TLjXeROD2p5H7oLGn8MKfy9HT github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/evanw/esbuild v0.14.39 h1:1TMZtCXOY4ctAbGY4QT9sjT203I/cQ16vXt2F9rLT58= -github.com/evanw/esbuild v0.14.39/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY= +github.com/evanw/esbuild v0.14.53 h1:9uU73SZUmP1jRQhaC6hPm9aoqFGYlPwfk7OrhG6AhpQ= +github.com/evanw/esbuild v0.14.53/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -1484,7 +1484,6 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1495,8 +1494,8 @@ golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=