mirror of
https://github.com/wavetermdev/backup.git
synced 2026-04-22 15:26:58 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f9f02d1cb |
@@ -41,7 +41,6 @@ jobs:
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
|
||||
echo $PWD >> $GITHUB_PATH
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
@@ -54,7 +53,7 @@ jobs:
|
||||
- name: Install Yarn Dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||
run: scripthaus run ${{ matrix.scripthaus }}
|
||||
run: ./scripthaus/scripthaus run ${{ matrix.scripthaus }}
|
||||
env:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE}}
|
||||
|
||||
@@ -47,32 +47,6 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout Scripthaus (Go only)
|
||||
if: matrix.language == 'go'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: scripthaus-dev/scripthaus
|
||||
path: scripthaus
|
||||
|
||||
- name: Setup Go (Go only)
|
||||
uses: actions/setup-go@v5
|
||||
if: matrix.language == 'go'
|
||||
with:
|
||||
go-version: stable
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
scripthaus/go.sum
|
||||
|
||||
- name: Install Scripthaus (Go only)
|
||||
if: matrix.language == 'go'
|
||||
run: |
|
||||
go work use ./scripthaus;
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
|
||||
echo $PWD >> $GITHUB_PATH
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
@@ -87,14 +61,9 @@ jobs:
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild (not Go)
|
||||
if: matrix.language != 'go'
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Build (Go only)
|
||||
if: matrix.language == 'go'
|
||||
run: scripthaus run build-backend
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
|
||||
@@ -48,15 +48,8 @@ class TosModal extends React.Component<{}, {}> {
|
||||
<div className="item-inner">
|
||||
<div className="item-title">Telemetry</div>
|
||||
<div className="item-text">
|
||||
We only collect minimal <i>anonymous</i>
|
||||
<a
|
||||
target="_blank"
|
||||
href={util.makeExternLink("https://docs.waveterm.dev/reference/telemetry")}
|
||||
rel={"noopener"}
|
||||
>
|
||||
telemetry data
|
||||
</a>
|
||||
to help us understand how many people are using Wave.
|
||||
We only collect minimal <i>anonymous</i> telemetry data to help us understand
|
||||
how many people are using Wave.
|
||||
</div>
|
||||
<div className="item-field" style={{ marginTop: 2 }}>
|
||||
<Toggle
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
|
||||
&:hover .line-actions {
|
||||
background-color: var(--line-actions-bg-color);
|
||||
backdrop-filter: blur(8px);
|
||||
.line-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@@ -48,15 +48,12 @@ class SimpleImageRenderer extends React.Component<
|
||||
return (
|
||||
<div className="image-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob?.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (this.objUrl == null) {
|
||||
if (dataBlob.name?.endsWith(".svg")) {
|
||||
dataBlob = new Blob([dataBlob], { type: "image/svg+xml" }) as ExtBlob;
|
||||
}
|
||||
this.objUrl = URL.createObjectURL(dataBlob);
|
||||
}
|
||||
let opts = this.props.opts;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
.media-renderer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: var(--termpad);
|
||||
|
||||
video {
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as mobx from "mobx";
|
||||
import * as mobxReact from "mobx-react";
|
||||
import * as util from "@/util/util";
|
||||
import { GlobalModel } from "@/models";
|
||||
|
||||
import "./media.less";
|
||||
|
||||
@mobxReact.observer
|
||||
class SimpleMediaRenderer extends React.Component<
|
||||
{ data: ExtBlob; context: RendererContext; opts: RendererOpts; savedHeight: number; lineState: LineStateType },
|
||||
{}
|
||||
> {
|
||||
objUrl: string = null;
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.objUrl != null) {
|
||||
URL.revokeObjectURL(this.objUrl);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let dataBlob = this.props.data;
|
||||
if (dataBlob == null || dataBlob.notFound) {
|
||||
return (
|
||||
<div className="media-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: file {dataBlob && dataBlob.name ? JSON.stringify(dataBlob.name) : ""} not found
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let fileUrl = this.props.lineState["wave:fileurl"];
|
||||
if (util.isBlank(fileUrl)) {
|
||||
return (
|
||||
<div className="media-renderer" style={{ fontSize: this.props.opts.termFontSize }}>
|
||||
<div className="load-error-text">
|
||||
ERROR: no fileurl found (please use `mediaview` to view media files)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
let fullVideoUrl = GlobalModel.getBaseHostPort() + fileUrl;
|
||||
const opts = this.props.opts;
|
||||
const height = opts.idealSize.height - 10;
|
||||
const width = opts.maxSize.width - 10;
|
||||
return (
|
||||
<div className="media-renderer" style={{ height: height, width: width }}>
|
||||
<video controls>
|
||||
<source src={fullVideoUrl} />
|
||||
</video>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SimpleMediaRenderer };
|
||||
@@ -32,7 +32,7 @@ class SimplePdfRenderer extends React.Component<
|
||||
);
|
||||
}
|
||||
if (this.objUrl == null) {
|
||||
const pdfBlob = new File([dataBlob], dataBlob.name ?? "file.pdf", { type: "application/pdf" });
|
||||
const pdfBlob = new File([dataBlob], "test.pdf", { type: "application/pdf" });
|
||||
this.objUrl = URL.createObjectURL(pdfBlob);
|
||||
}
|
||||
const opts = this.props.opts;
|
||||
|
||||
@@ -8,7 +8,6 @@ import { SimpleMustacheRenderer } from "./mustache/mustache";
|
||||
import { CSVRenderer } from "./csv/csv";
|
||||
import { OpenAIRenderer, OpenAIRendererModel } from "./openai/openai";
|
||||
import { SimplePdfRenderer } from "./pdf/pdf";
|
||||
import { SimpleMediaRenderer } from "./media/media";
|
||||
import { isBlank } from "@/util/util";
|
||||
import { sprintf } from "sprintf-js";
|
||||
|
||||
@@ -90,16 +89,6 @@ const PluginConfigs: RendererPluginType[] = [
|
||||
mimeTypes: ["application/pdf"],
|
||||
simpleComponent: SimplePdfRenderer,
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
rendererType: "simple",
|
||||
heightType: "pixels",
|
||||
dataType: "blob",
|
||||
collapseType: "hide",
|
||||
globalCss: null,
|
||||
mimeTypes: ["video/*", "audio/*"],
|
||||
simpleComponent: SimpleMediaRenderer,
|
||||
},
|
||||
];
|
||||
|
||||
class PluginModelClass {
|
||||
|
||||
@@ -74,6 +74,7 @@ var BuildTime = "0"
|
||||
|
||||
var GlobalLock = &sync.Mutex{}
|
||||
var WSStateMap = make(map[string]*scws.WSState) // clientid -> WsState
|
||||
var GlobalAuthKey string
|
||||
var shutdownOnce sync.Once
|
||||
var ContentTypeHeaderValidRe = regexp.MustCompile(`^\w+/[\w.+-]+$`)
|
||||
|
||||
@@ -139,7 +140,7 @@ func HandleWs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
state := getWSState(clientId)
|
||||
if state == nil {
|
||||
state = scws.MakeWSState(clientId, scbase.WaveAuthKey)
|
||||
state = scws.MakeWSState(clientId, GlobalAuthKey)
|
||||
state.ReplaceShell(shell)
|
||||
setWSState(state)
|
||||
} else {
|
||||
@@ -686,7 +687,7 @@ func AuthKeyMiddleWare(next http.Handler) http.Handler {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
if reqAuthKey != scbase.WaveAuthKey {
|
||||
if reqAuthKey != GlobalAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -706,14 +707,14 @@ func AuthKeyWrapAllowHmac(fn WebFnType) WebFnType {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
hmacOk, err := promptenc.ValidateUrlHmac([]byte(scbase.WaveAuthKey), r.URL.Path, qvals)
|
||||
hmacOk, err := promptenc.ValidateUrlHmac([]byte(GlobalAuthKey), r.URL.Path, qvals)
|
||||
if err != nil || !hmacOk {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(fmt.Sprintf("error validating hmac")))
|
||||
return
|
||||
}
|
||||
// fallthrough (hmac is valid)
|
||||
} else if reqAuthKey != scbase.WaveAuthKey {
|
||||
} else if reqAuthKey != GlobalAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -732,7 +733,7 @@ func AuthKeyWrap(fn WebFnType) WebFnType {
|
||||
w.Write([]byte("no x-authkey header"))
|
||||
return
|
||||
}
|
||||
if reqAuthKey != scbase.WaveAuthKey {
|
||||
if reqAuthKey != GlobalAuthKey {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("x-authkey header is invalid"))
|
||||
return
|
||||
@@ -889,11 +890,12 @@ func main() {
|
||||
}
|
||||
return
|
||||
}
|
||||
err = scbase.InitializeWaveAuthKey()
|
||||
authKey, err := scbase.ReadWaveAuthKey()
|
||||
if err != nil {
|
||||
log.Printf("[error] %v\n", err)
|
||||
return
|
||||
}
|
||||
GlobalAuthKey = authKey
|
||||
err = sstore.TryMigrateUp()
|
||||
if err != nil {
|
||||
log.Printf("[error] migrate up: %v\n", err)
|
||||
|
||||
@@ -37,7 +37,6 @@ import (
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/comp"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/dbutil"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/promptenc"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote"
|
||||
"github.com/wavetermdev/waveterm/wavesrv/pkg/remote/openai"
|
||||
@@ -281,7 +280,6 @@ func init() {
|
||||
registerCmdFn("mdview", MarkdownViewCommand)
|
||||
registerCmdFn("markdownview", MarkdownViewCommand)
|
||||
registerCmdFn("pdfview", PdfViewCommand)
|
||||
registerCmdFn("mediaview", MediaViewCommand)
|
||||
|
||||
registerCmdFn("csvview", CSVViewCommand)
|
||||
}
|
||||
@@ -4863,12 +4861,11 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
if pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("%s argument cannot be empty", GetCmdStr(pk))
|
||||
}
|
||||
filePath := pk.Args[0]
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), filePath)
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), pk.Args[0])
|
||||
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
@@ -4877,7 +4874,7 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc
|
||||
// set the line state
|
||||
lineState := make(map[string]any)
|
||||
lineState[sstore.LineState_Source] = "file"
|
||||
lineState[sstore.LineState_File] = filePath
|
||||
lineState[sstore.LineState_File] = pk.Args[0]
|
||||
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "image", lineState)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
@@ -4918,58 +4915,6 @@ func PdfViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbu
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MakeReadFileUrl(screenId string, lineId string, filePath string) (string, error) {
|
||||
qvals := make(url.Values)
|
||||
qvals.Set("screenid", screenId)
|
||||
qvals.Set("lineid", lineId)
|
||||
qvals.Set("path", filePath)
|
||||
qvals.Set("nonce", uuid.New().String())
|
||||
hmacStr, err := promptenc.ComputeUrlHmac([]byte(scbase.WaveAuthKey), "/api/read-file", qvals)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error computing hmac-url: %v", err)
|
||||
}
|
||||
qvals.Set("hmac", hmacStr)
|
||||
return "/api/read-file?" + qvals.Encode(), nil
|
||||
}
|
||||
|
||||
func MediaViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
}
|
||||
// TODO more error checking on filename format?
|
||||
if pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("%s argument cannot be empty", GetCmdStr(pk))
|
||||
}
|
||||
fileName := pk.Args[0]
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_RemoteConnected)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outputStr := fmt.Sprintf("%s %q", GetCmdStr(pk), fileName)
|
||||
cmd, err := makeStaticCmd(ctx, GetCmdStr(pk), ids, pk.GetRawStr(), []byte(outputStr))
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
// compute hmac read-file URL
|
||||
readFileUrl, err := MakeReadFileUrl(ids.ScreenId, cmd.LineId, fileName)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, fmt.Errorf("error making read-file url: %v", err)
|
||||
}
|
||||
// set the line state
|
||||
lineState := make(map[string]any)
|
||||
lineState[sstore.LineState_FileUrl] = readFileUrl
|
||||
lineState[sstore.LineState_File] = fileName
|
||||
update, err := addLineForCmd(ctx, "/"+GetCmdStr(pk), false, ids, cmd, "media", lineState)
|
||||
if err != nil {
|
||||
// TODO tricky error since the command was a success, but we can't show the output
|
||||
return nil, err
|
||||
}
|
||||
update.AddUpdate(sstore.InteractiveUpdate(pk.Interactive))
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("%s requires an argument (file name)", GetCmdStr(pk))
|
||||
|
||||
@@ -36,7 +36,6 @@ var BareMetaCmds = []BareMetaCmdDecl{
|
||||
{"mdview", "markdownview"},
|
||||
{"csvview", "csvview"},
|
||||
{"pdfview", "pdfview"},
|
||||
{"mediaview", "mediaview"},
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func ComputeUrlHmac(key []byte, baseUrl string, qvals url.Values) (string, error) {
|
||||
if !qvals.Has("nonce") {
|
||||
if qvals.Has("nonce") {
|
||||
return "", fmt.Errorf("nonce is required for hmac")
|
||||
}
|
||||
if qvals.Has("hmac") {
|
||||
@@ -37,7 +37,7 @@ func ValidateUrlHmac(key []byte, baseUrl string, qvalsOrig url.Values) (bool, er
|
||||
qvals := copyUrlValues(qvalsOrig)
|
||||
hmacStr := qvals.Get("hmac")
|
||||
if hmacStr == "" {
|
||||
return false, fmt.Errorf("no hmac key found")
|
||||
return false, fmt.Errorf("no hmac key found"))
|
||||
}
|
||||
qvals.Del("hmac")
|
||||
encStr := baseUrl + "?" + qvals.Encode()
|
||||
|
||||
@@ -38,9 +38,6 @@ const WaveAppPathVarName = "WAVETERM_APP_PATH"
|
||||
const WaveAuthKeyFileName = "waveterm.authkey"
|
||||
const MShellVersion = "v0.5.0"
|
||||
|
||||
// initialized by InitialzeWaveAuthKey (called by main-server)
|
||||
var WaveAuthKey string
|
||||
|
||||
var SessionDirCache = make(map[string]string)
|
||||
var ScreenDirCache = make(map[string]string)
|
||||
var BaseLock = &sync.Mutex{}
|
||||
@@ -111,28 +108,25 @@ func MShellBinaryReader(version string, goos string, goarch string) (io.ReadClos
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// also sets WaveAuthKey
|
||||
func createWaveAuthKeyFile(fileName string) error {
|
||||
func createWaveAuthKeyFile(fileName string) (string, error) {
|
||||
fd, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
defer fd.Close()
|
||||
keyStr := GenWaveUUID()
|
||||
_, err = fd.Write([]byte(keyStr))
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
WaveAuthKey = keyStr
|
||||
return nil
|
||||
return keyStr, nil
|
||||
}
|
||||
|
||||
// sets WaveAuthKey
|
||||
func InitializeWaveAuthKey() error {
|
||||
func ReadWaveAuthKey() (string, error) {
|
||||
homeDir := GetWaveHomeDir()
|
||||
err := ensureDir(homeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
|
||||
return "", fmt.Errorf("cannot find/create WAVETERM_HOME directory %q", homeDir)
|
||||
}
|
||||
fileName := path.Join(homeDir, WaveAuthKeyFileName)
|
||||
fd, err := os.Open(fileName)
|
||||
@@ -140,20 +134,19 @@ func InitializeWaveAuthKey() error {
|
||||
return createWaveAuthKeyFile(fileName)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening wave authkey:%s: %v", fileName, err)
|
||||
return "", fmt.Errorf("error opening wave authkey:%s: %v", fileName, err)
|
||||
}
|
||||
defer fd.Close()
|
||||
buf, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading wave authkey:%s: %v", fileName, err)
|
||||
return "", fmt.Errorf("error reading wave authkey:%s: %v", fileName, err)
|
||||
}
|
||||
keyStr := string(buf)
|
||||
_, err = uuid.Parse(keyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid authkey:%s format: %v", fileName, err)
|
||||
return "", fmt.Errorf("invalid authkey:%s format: %v", fileName, err)
|
||||
}
|
||||
WaveAuthKey = keyStr
|
||||
return nil
|
||||
return keyStr, nil
|
||||
}
|
||||
|
||||
func AcquireWaveLock() (*os.File, error) {
|
||||
|
||||
@@ -63,7 +63,6 @@ const (
|
||||
const (
|
||||
LineState_Source = "prompt:source"
|
||||
LineState_File = "prompt:file"
|
||||
LineState_FileUrl = "wave:fileurl"
|
||||
LineState_Min = "wave:min"
|
||||
LineState_Template = "template"
|
||||
LineState_Mode = "mode"
|
||||
|
||||
@@ -4016,9 +4016,9 @@ fn.name@1.x.x:
|
||||
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.15.6"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b"
|
||||
integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==
|
||||
version "1.15.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf"
|
||||
integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==
|
||||
|
||||
foreground-child@^3.1.0:
|
||||
version "3.1.1"
|
||||
|
||||
Reference in New Issue
Block a user