diff --git a/.github/workflows/cross-wasm.yml b/.github/workflows/cross-wasm.yml
index f98f604de..7caeb0add 100644
--- a/.github/workflows/cross-wasm.yml
+++ b/.github/workflows/cross-wasm.yml
@@ -38,7 +38,9 @@ jobs:
- name: tsconnect static build
# Use our custom Go toolchain, we set build tags (to control binary size)
# that depend on it.
- run: ./tool/go run ./cmd/tsconnect --fast-compression build
+ run: |
+ ./tool/go run ./cmd/tsconnect --fast-compression build
+ ./tool/go run ./cmd/tsconnect build-pkg
- uses: k0kubun/action-slack@v2.0.0
with:
diff --git a/cmd/tsconnect/.gitignore b/cmd/tsconnect/.gitignore
index b94707787..13615d121 100644
--- a/cmd/tsconnect/.gitignore
+++ b/cmd/tsconnect/.gitignore
@@ -1,2 +1,3 @@
node_modules/
-dist/
+/dist
+/pkg
diff --git a/cmd/tsconnect/README.md b/cmd/tsconnect/README.md
index f4a01ffd7..fc01876a3 100644
--- a/cmd/tsconnect/README.md
+++ b/cmd/tsconnect/README.md
@@ -28,3 +28,13 @@ To serve them, run:
```
By default the build output is placed in the `dist/` directory and embedded in the binary, but this can be controlled by the `-distdir` flag. The `-addr` flag controls the interface and port that the serve listens on.
+
+# Library / NPM Package
+
+The client is also available as an NPM package. To build it, run:
+
+```
+./tool/go run ./cmd/tsconnect build-pkg
+```
+
+That places the output in the `pkg/` directory, which may then be uploaded to a package registry (or installed from the file path directly).
diff --git a/cmd/tsconnect/build-pkg.go b/cmd/tsconnect/build-pkg.go
new file mode 100644
index 000000000..75b4f4cab
--- /dev/null
+++ b/cmd/tsconnect/build-pkg.go
@@ -0,0 +1,44 @@
+// 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.
+
+package main
+
+import (
+ "log"
+
+ esbuild "github.com/evanw/esbuild/pkg/api"
+)
+
+func runBuildPkg() {
+ buildOptions, err := commonSetup(prodMode)
+ if err != nil {
+ log.Fatalf("Cannot setup: %v", err)
+ }
+
+ log.Printf("Linting...\n")
+ if err := runYarn("lint"); err != nil {
+ log.Fatalf("Linting failed: %v", err)
+ }
+
+ if err := cleanDir(*pkgDir, "package.json"); err != nil {
+ log.Fatalf("Cannot clean %s: %v", *pkgDir, err)
+ }
+
+ buildOptions.EntryPoints = []string{"src/pkg/pkg.ts", "src/pkg/pkg.css"}
+ buildOptions.Outdir = *pkgDir
+ buildOptions.Format = esbuild.FormatESModule
+ buildOptions.AssetNames = "[name]"
+ buildOptions.Write = true
+ buildOptions.MinifyWhitespace = true
+ buildOptions.MinifyIdentifiers = true
+ buildOptions.MinifySyntax = true
+
+ runEsbuild(*buildOptions)
+
+ log.Printf("Generating types...\n")
+ if err := runYarn("pkg-types"); err != nil {
+ log.Fatalf("Type generation failed: %v", err)
+ }
+
+}
diff --git a/cmd/tsconnect/build.go b/cmd/tsconnect/build.go
index 5a9607a57..d1de58512 100644
--- a/cmd/tsconnect/build.go
+++ b/cmd/tsconnect/build.go
@@ -13,7 +13,6 @@
"path"
"path/filepath"
- esbuild "github.com/evanw/esbuild/pkg/api"
"tailscale.com/util/precompress"
)
@@ -28,7 +27,7 @@ func runBuild() {
log.Fatalf("Linting failed: %v", err)
}
- if err := cleanDist(); err != nil {
+ if err := cleanDir(*distDir, "placeholder"); err != nil {
log.Fatalf("Cannot clean %s: %v", *distDir, err)
}
@@ -41,21 +40,7 @@ func runBuild() {
buildOptions.AssetNames = "[name]-[hash]"
buildOptions.Metafile = true
- log.Printf("Running esbuild...\n")
- result := esbuild.Build(*buildOptions)
- if len(result.Errors) > 0 {
- log.Printf("ESBuild Error:\n")
- for _, e := range result.Errors {
- log.Printf("%v", e)
- }
- log.Fatal("Build failed")
- }
- if len(result.Warnings) > 0 {
- log.Printf("ESBuild Warnings:\n")
- for _, w := range result.Warnings {
- log.Printf("%v", w)
- }
- }
+ result := runEsbuild(*buildOptions)
// Preserve build metadata so we can extract hashed file names for serving.
metadataBytes, err := fixEsbuildMetadataPaths(result.Metafile)
@@ -98,8 +83,6 @@ func fixEsbuildMetadataPaths(metadataStr string) ([]byte, error) {
return json.Marshal(metadata)
}
-// cleanDist removes files from the dist build directory, except the placeholder
-// one that we keep to make sure Git still creates the directory.
func cleanDist() error {
log.Printf("Cleaning %s...\n", *distDir)
files, err := os.ReadDir(*distDir)
diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go
index 557ad77fd..da823829a 100644
--- a/cmd/tsconnect/common.go
+++ b/cmd/tsconnect/common.go
@@ -17,6 +17,7 @@
"time"
esbuild "github.com/evanw/esbuild/pkg/api"
+ "golang.org/x/exp/slices"
)
const (
@@ -38,7 +39,7 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
}
return &esbuild.BuildOptions{
- EntryPoints: []string{"src/index.ts", "src/index.css"},
+ EntryPoints: []string{"src/app/index.ts", "src/app/index.css"},
Outdir: *distDir,
Bundle: true,
Sourcemap: esbuild.SourceMapLinked,
@@ -67,6 +68,47 @@ func commonSetup(dev bool) (*esbuild.BuildOptions, error) {
}, nil
}
+// cleanDir removes files from dirPath, except the ones specified by
+// preserveFiles.
+func cleanDir(dirPath string, preserveFiles ...string) error {
+ log.Printf("Cleaning %s...\n", dirPath)
+ files, err := os.ReadDir(dirPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return os.MkdirAll(dirPath, 0755)
+ }
+ return err
+ }
+
+ for _, file := range files {
+ if !slices.Contains(preserveFiles, file.Name()) {
+ if err := os.Remove(filepath.Join(dirPath, file.Name())); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func runEsbuild(buildOptions esbuild.BuildOptions) esbuild.BuildResult {
+ log.Printf("Running esbuild...\n")
+ result := esbuild.Build(buildOptions)
+ if len(result.Errors) > 0 {
+ log.Printf("ESBuild Error:\n")
+ for _, e := range result.Errors {
+ log.Printf("%v", e)
+ }
+ log.Fatal("Build failed")
+ }
+ if len(result.Warnings) > 0 {
+ log.Printf("ESBuild Warnings:\n")
+ for _, w := range result.Warnings {
+ log.Printf("%v", w)
+ }
+ }
+ return result
+}
+
// setupEsbuildWasmExecJS generates an esbuild plugin that serves the current
// wasm_exec.js runtime helper library from the Go toolchain.
func setupEsbuildWasmExecJS(build esbuild.PluginBuild) {
@@ -167,7 +209,7 @@ type EsbuildMetadata struct {
func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) {
build.OnLoad(esbuild.OnLoadOptions{
- Filter: "./src/index.css$",
+ Filter: "./src/.*\\.css$",
}, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) {
start := time.Now()
yarnArgs := []string{"--silent", "tailwind", "-i", args.Path}
diff --git a/cmd/tsconnect/package.json b/cmd/tsconnect/package.json
index 06bee1e13..28ebd6762 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",
+ "dts-bundle-generator": "^6.12.0",
"preact": "^10.10.0",
"qrcode": "^1.5.0",
"tailwindcss": "^3.1.6",
@@ -13,7 +14,8 @@
"xterm-addon-fit": "^0.5.0"
},
"scripts": {
- "lint": "tsc --noEmit"
+ "lint": "tsc --noEmit",
+ "pkg-types": "dts-bundle-generator --inline-declare-global=true --no-banner -o pkg/pkg.d.ts src/pkg/pkg.ts"
},
"prettier": {
"semi": false,
diff --git a/cmd/tsconnect/pkg/package.json b/cmd/tsconnect/pkg/package.json
new file mode 100644
index 000000000..366a63f96
--- /dev/null
+++ b/cmd/tsconnect/pkg/package.json
@@ -0,0 +1,10 @@
+{
+ "author": "Tailscale Inc.",
+ "description": "Tailscale Connect SDK",
+ "license": "BSD-3-Clause",
+ "name": "@tailscale/connect",
+ "type": "module",
+ "main": "./pkg.js",
+ "types": "./pkg.d.ts",
+ "version": "0.0.5"
+}
diff --git a/cmd/tsconnect/serve.go b/cmd/tsconnect/serve.go
index d0baf4480..275c96cb5 100644
--- a/cmd/tsconnect/serve.go
+++ b/cmd/tsconnect/serve.go
@@ -115,8 +115,8 @@ func generateServeIndex(distFS fs.FS) ([]byte, error) {
}
var entryPointsToDefaultDistPaths = map[string]string{
- "src/index.css": "dist/index.css",
- "src/index.ts": "dist/index.js",
+ "src/app/index.css": "dist/index.css",
+ "src/app/index.ts": "dist/index.js",
}
func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) {
diff --git a/cmd/tsconnect/src/app.tsx b/cmd/tsconnect/src/app/app.tsx
similarity index 90%
rename from cmd/tsconnect/src/app.tsx
rename to cmd/tsconnect/src/app/app.tsx
index 27c44d3c7..31102a9f6 100644
--- a/cmd/tsconnect/src/app.tsx
+++ b/cmd/tsconnect/src/app/app.tsx
@@ -3,7 +3,6 @@
// 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"
@@ -18,7 +17,7 @@ type AppState = {
}
class App extends Component<{}, AppState> {
- state: AppState = { ipnState: IPNState.NoState }
+ state: AppState = { ipnState: "NoState" }
#goPanicTimeout?: number
render() {
@@ -37,7 +36,7 @@ class App extends Component<{}, AppState> {
}
let machineAuthInstructions
- if (ipnState === IPNState.NeedsMachineAuth) {
+ if (ipnState === "NeedsMachineAuth") {
machineAuthInstructions = (
An administrator needs to authorize this device.
@@ -46,7 +45,7 @@ class App extends Component<{}, AppState> {
}
let ssh
- if (ipn && ipnState === IPNState.Running && netMap) {
+ if (ipn && ipnState === "Running" && netMap) {
ssh =
}
@@ -77,9 +76,9 @@ class App extends Component<{}, AppState> {
handleIPNState = (state: IPNState) => {
const { ipn } = this.state
this.setState({ ipnState: state })
- if (state == IPNState.NeedsLogin) {
+ if (state === "NeedsLogin") {
ipn?.login()
- } else if ([IPNState.Running, IPNState.NeedsMachineAuth].includes(state)) {
+ } else if (["Running", "NeedsMachineAuth"].includes(state)) {
this.setState({ browseToURL: undefined })
}
}
diff --git a/cmd/tsconnect/src/go-panic-display.tsx b/cmd/tsconnect/src/app/go-panic-display.tsx
similarity index 100%
rename from cmd/tsconnect/src/go-panic-display.tsx
rename to cmd/tsconnect/src/app/go-panic-display.tsx
diff --git a/cmd/tsconnect/src/header.tsx b/cmd/tsconnect/src/app/header.tsx
similarity index 71%
rename from cmd/tsconnect/src/header.tsx
rename to cmd/tsconnect/src/app/header.tsx
index ff08adef9..67d5ae31e 100644
--- a/cmd/tsconnect/src/header.tsx
+++ b/cmd/tsconnect/src/app/header.tsx
@@ -2,13 +2,11 @@
// 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) {
+ if (state === "Running") {
logoutButton = (
+///
+
+import "../wasm_exec"
+import wasmURL from "./main.wasm"
+
+/**
+ * Superset of the IPNConfig type, with additional configuration that is
+ * needed for the package to function.
+ */
+type IPNPackageConfig = IPNConfig & {
+ // Auth key used to intitialize the Tailscale client (required)
+ authKey: string
+ // URL of the main.wasm file that is included in the page, if it is not
+ // accessible via a relative URL.
+ wasmURL?: string
+ // Funtion invoked if the Go process panics or unexpectedly exits.
+ panicHandler: (err: string) => void
+}
+
+export async function createIPN(config: IPNPackageConfig): Promise {
+ const go = new Go()
+ const wasmInstance = await WebAssembly.instantiateStreaming(
+ fetch(config.wasmURL ?? wasmURL),
+ go.importObject
+ )
+ // The Go process should never exit, if it does then it's an unhandled panic.
+ go.run(wasmInstance.instance).then(() =>
+ config.panicHandler("Unexpected shutdown")
+ )
+
+ return newIPN(config)
+}
diff --git a/cmd/tsconnect/src/esbuild.d.ts b/cmd/tsconnect/src/types/esbuild.d.ts
similarity index 100%
rename from cmd/tsconnect/src/esbuild.d.ts
rename to cmd/tsconnect/src/types/esbuild.d.ts
diff --git a/cmd/tsconnect/src/wasm_js.ts b/cmd/tsconnect/src/types/wasm_js.d.ts
similarity index 72%
rename from cmd/tsconnect/src/wasm_js.ts
rename to cmd/tsconnect/src/types/wasm_js.d.ts
index a33f0685a..9d6622a7c 100644
--- a/cmd/tsconnect/src/wasm_js.ts
+++ b/cmd/tsconnect/src/types/wasm_js.d.ts
@@ -4,8 +4,7 @@
/**
* @fileoverview Type definitions for types exported by the wasm_js.go Go
- * module. Not actually a .d.ts file so that we can use enums from it in
- * esbuild's simplified TypeScript compiler (see https://github.com/evanw/esbuild/issues/2298#issuecomment-1146378367)
+ * module.
*/
declare global {
@@ -26,9 +25,7 @@ declare global {
onDone: () => void
}
): IPNSSHSession
- fetch(
- url: string
- ): Promise<{
+ fetch(url: string): Promise<{
status: number
statusText: string
text: () => Promise
@@ -48,6 +45,7 @@ declare global {
type IPNConfig = {
stateStorage?: IPNStateStorage
authKey?: string
+ controlURL?: string
}
type IPNCallbacks = {
@@ -77,23 +75,23 @@ declare global {
online?: boolean
tailscaleSSHEnabled: boolean
}
+
+ /** Mirrors values from ipn/backend.go */
+ type IPNState =
+ | "NoState"
+ | "InUseOtherUser"
+ | "NeedsLogin"
+ | "NeedsMachineAuth"
+ | "Stopped"
+ | "Starting"
+ | "Running"
+
+ /** Mirrors values from MachineStatus in tailcfg.go */
+ type IPNMachineStatus =
+ | "MachineUnknown"
+ | "MachineUnauthorized"
+ | "MachineAuthorized"
+ | "MachineInvalid"
}
-/** Mirrors values from ipn/backend.go */
-export const enum IPNState {
- NoState = 0,
- InUseOtherUser = 1,
- NeedsLogin = 2,
- NeedsMachineAuth = 3,
- Stopped = 4,
- Starting = 5,
- Running = 6,
-}
-
-/** Mirrors values from MachineStatus in tailcfg.go */
-export const enum IPNMachineStatus {
- MachineUnknown = 0,
- MachineUnauthorized = 1,
- MachineAuthorized = 2,
- MachineInvalid = 3,
-}
+export {}
diff --git a/cmd/tsconnect/tsconnect.go b/cmd/tsconnect/tsconnect.go
index f284bd2f2..91feaf727 100644
--- a/cmd/tsconnect/tsconnect.go
+++ b/cmd/tsconnect/tsconnect.go
@@ -20,6 +20,7 @@
var (
addr = flag.String("addr", ":9090", "address to listen on")
distDir = flag.String("distdir", "./dist", "path of directory to place build output in")
+ pkgDir = flag.String("pkgdir", "./pkg", "path of directory to place NPM package build output in")
yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies")
fastCompression = flag.Bool("fast-compression", false, "Use faster compression when building, to speed up build time. Meant to iterative/debugging use only.")
devControl = flag.String("dev-control", "", "URL of a development control server to be used with dev. If provided without specifying dev, an error will be returned.")
@@ -37,6 +38,8 @@ func main() {
runDev()
case "build":
runBuild()
+ case "build-pkg":
+ runBuildPkg()
case "serve":
runServe()
default:
diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go
index 112d37229..18abef044 100644
--- a/cmd/tsconnect/wasm/wasm_js.go
+++ b/cmd/tsconnect/wasm/wasm_js.go
@@ -69,6 +69,12 @@ func newIPN(jsConfig js.Value) map[string]any {
store = &jsStateStore{jsStateStorage}
}
+ jsControlURL := jsConfig.Get("controlURL")
+ controlURL := ControlURL
+ if jsControlURL.Type() == js.TypeString {
+ controlURL = jsControlURL.String()
+ }
+
jsAuthKey := jsConfig.Get("authKey")
var authKey string
if jsAuthKey.Type() == js.TypeString {
@@ -125,9 +131,11 @@ func newIPN(jsConfig js.Value) map[string]any {
ns.SetLocalBackend(lb)
jsIPN := &jsIPN{
- dialer: dialer,
- srv: srv,
- lb: lb,
+ dialer: dialer,
+ srv: srv,
+ lb: lb,
+ controlURL: controlURL,
+ authKey: authKey,
}
return map[string]any{
@@ -141,7 +149,7 @@ func newIPN(jsConfig js.Value) map[string]any {
})`)
return nil
}
- jsIPN.run(args[0], authKey)
+ jsIPN.run(args[0])
return nil
}),
"login": js.FuncOf(func(this js.Value, args []js.Value) interface{} {
@@ -183,14 +191,33 @@ func newIPN(jsConfig js.Value) map[string]any {
}
type jsIPN struct {
- dialer *tsdial.Dialer
- srv *ipnserver.Server
- lb *ipnlocal.LocalBackend
+ dialer *tsdial.Dialer
+ srv *ipnserver.Server
+ lb *ipnlocal.LocalBackend
+ controlURL string
+ authKey string
}
-func (i *jsIPN) run(jsCallbacks js.Value, authKey string) {
+var jsIPNState = map[ipn.State]string{
+ ipn.NoState: "NoState",
+ ipn.InUseOtherUser: "InUseOtherUser",
+ ipn.NeedsLogin: "NeedsLogin",
+ ipn.NeedsMachineAuth: "NeedsMachineAuth",
+ ipn.Stopped: "Stopped",
+ ipn.Starting: "Starting",
+ ipn.Running: "Running",
+}
+
+var jsMachineStatus = map[tailcfg.MachineStatus]string{
+ tailcfg.MachineUnknown: "MachineUnknown",
+ tailcfg.MachineUnauthorized: "MachineUnauthorized",
+ tailcfg.MachineAuthorized: "MachineAuthorized",
+ tailcfg.MachineInvalid: "MachineInvalid",
+}
+
+func (i *jsIPN) run(jsCallbacks js.Value) {
notifyState := func(state ipn.State) {
- jsCallbacks.Call("notifyState", int(state))
+ jsCallbacks.Call("notifyState", jsIPNState[state])
}
notifyState(ipn.NoState)
@@ -218,7 +245,7 @@ func (i *jsIPN) run(jsCallbacks js.Value, authKey string) {
NodeKey: nm.NodeKey.String(),
MachineKey: nm.MachineKey.String(),
},
- MachineStatus: int(nm.MachineStatus),
+ MachineStatus: jsMachineStatus[nm.MachineStatus],
},
Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode {
name := p.Name
@@ -253,13 +280,13 @@ func (i *jsIPN) run(jsCallbacks js.Value, authKey string) {
err := i.lb.Start(ipn.Options{
StateKey: "wasm",
UpdatePrefs: &ipn.Prefs{
- ControlURL: ControlURL,
+ ControlURL: i.controlURL,
RouteAll: false,
AllowSingleHosts: true,
WantRunning: true,
Hostname: generateHostname(),
},
- AuthKey: authKey,
+ AuthKey: i.authKey,
})
if err != nil {
log.Printf("Start error: %v", err)
@@ -467,7 +494,7 @@ type jsNetMapNode struct {
type jsNetMapSelfNode struct {
jsNetMapNode
- MachineStatus int `json:"machineStatus"`
+ MachineStatus string `json:"machineStatus"`
}
type jsNetMapPeerNode struct {
diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock
index d9dad03fb..cb311ada9 100644
--- a/cmd/tsconnect/yarn.lock
+++ b/cmd/tsconnect/yarn.lock
@@ -130,6 +130,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -181,6 +190,14 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+dts-bundle-generator@^6.12.0:
+ version "6.12.0"
+ resolved "https://registry.yarnpkg.com/dts-bundle-generator/-/dts-bundle-generator-6.12.0.tgz#0a221bdce5fdd309a56c8556e645f16ed87ab07d"
+ integrity sha512-k/QAvuVaLIdyWRUHduDrWBe4j8PcE6TDt06+f32KHbW7/SmUPbX1O23fFtQgKwUyTBkbIjJFOFtNrF97tJcKug==
+ dependencies:
+ typescript ">=3.0.1"
+ yargs "^17.2.1"
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -191,6 +208,11 @@ encode-utf8@^1.0.3:
resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
@@ -234,7 +256,7 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
-get-caller-file@^2.0.1:
+get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -523,7 +545,7 @@ source-map-js@^1.0.2:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
-string-width@^4.1.0, string-width@^4.2.0:
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -579,7 +601,7 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
-typescript@^4.7.4:
+typescript@>=3.0.1, typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
@@ -603,6 +625,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
@@ -623,6 +654,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
@@ -636,6 +672,11 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^21.0.0:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
yargs@^15.3.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
@@ -652,3 +693,16 @@ yargs@^15.3.1:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
+
+yargs@^17.2.1:
+ version "17.5.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
+ integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.0.0"