diff --git a/apps/cli/cmd/flow.go b/apps/cli/cmd/flow.go
index 3935bfeea..510547c10 100644
--- a/apps/cli/cmd/flow.go
+++ b/apps/cli/cmd/flow.go
@@ -184,6 +184,11 @@ var yamlflowRunCmd = &cobra.Command{
&services.NodeAiProvider,
&services.NodeMemory,
&services.NodeGraphQL,
+ &services.NodeWsConnection,
+ &services.NodeWsSend,
+ &services.NodeWait,
+ &services.WebSocket,
+ &services.WebSocketHeader,
&services.GraphQL,
&services.GraphQLHeader,
&services.Workspace,
diff --git a/apps/cli/internal/common/services.go b/apps/cli/internal/common/services.go
index f2657757c..ee75bfeb1 100644
--- a/apps/cli/internal/common/services.go
+++ b/apps/cli/internal/common/services.go
@@ -12,6 +12,7 @@ import (
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sflow"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sgraphql"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/shttp"
+ "github.com/the-dev-tools/dev-tools/packages/server/pkg/service/swebsocket"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sworkspace"
)
@@ -32,16 +33,23 @@ type Services struct {
FlowVariable sflow.FlowVariableService
// Flow Nodes
- Node sflow.NodeService
- NodeRequest sflow.NodeRequestService
- NodeFor sflow.NodeForService
- NodeForEach sflow.NodeForEachService
- NodeIf sflow.NodeIfService
- NodeJS sflow.NodeJsService
- NodeAI sflow.NodeAIService
- NodeAiProvider sflow.NodeAiProviderService
- NodeMemory sflow.NodeMemoryService
- NodeGraphQL sflow.NodeGraphQLService
+ Node sflow.NodeService
+ NodeRequest sflow.NodeRequestService
+ NodeFor sflow.NodeForService
+ NodeForEach sflow.NodeForEachService
+ NodeIf sflow.NodeIfService
+ NodeJS sflow.NodeJsService
+ NodeAI sflow.NodeAIService
+ NodeAiProvider sflow.NodeAiProviderService
+ NodeMemory sflow.NodeMemoryService
+ NodeGraphQL sflow.NodeGraphQLService
+ NodeWsConnection sflow.NodeWsConnectionService
+ NodeWsSend sflow.NodeWsSendService
+ NodeWait sflow.NodeWaitService
+
+ // WebSocket
+ WebSocket swebsocket.WebSocketService
+ WebSocketHeader swebsocket.WebSocketHeaderService
// GraphQL
GraphQL sgraphql.GraphQLService
@@ -94,7 +102,14 @@ func CreateServices(ctx context.Context, db *sql.DB, logger *slog.Logger) (*Serv
NodeAI: sflow.NewNodeAIService(queries),
NodeAiProvider: sflow.NewNodeAiProviderService(queries),
NodeMemory: sflow.NewNodeMemoryService(queries),
- NodeGraphQL: sflow.NewNodeGraphQLService(queries),
+ NodeGraphQL: sflow.NewNodeGraphQLService(queries),
+ NodeWsConnection: sflow.NewNodeWsConnectionService(queries),
+ NodeWsSend: sflow.NewNodeWsSendService(queries),
+ NodeWait: sflow.NewNodeWaitService(queries),
+
+ // WebSocket
+ WebSocket: swebsocket.New(queries, logger),
+ WebSocketHeader: swebsocket.NewWebSocketHeaderService(queries),
// GraphQL
GraphQL: sgraphql.New(queries, logger),
diff --git a/apps/cli/internal/runner/runner.go b/apps/cli/internal/runner/runner.go
index d4ae4e6b1..325da9dc9 100644
--- a/apps/cli/internal/runner/runner.go
+++ b/apps/cli/internal/runner/runner.go
@@ -264,7 +264,7 @@ func RunFlow(ctx context.Context, flowPtr *mflow.Flow, services RunnerServices,
defer close(gqlRespChan)
// Build flow node map using flowbuilder
- flowNodeMap, startNodeID, err := services.Builder.BuildNodes(
+ flowNodeMap, startNodeIDs, err := services.Builder.BuildNodes(
ctx,
*flowPtr,
nodes,
@@ -279,7 +279,7 @@ func RunFlow(ctx context.Context, flowPtr *mflow.Flow, services RunnerServices,
}
// Use the same timeout for the flow runner
- runnerInst := flowlocalrunner.CreateFlowRunner(idwrap.NewNow(), latestFlowID, startNodeID, flowNodeMap, edgeMap, nodeTimeout, nil)
+ runnerInst := flowlocalrunner.CreateFlowRunner(idwrap.NewNow(), latestFlowID, startNodeIDs, flowNodeMap, edgeMap, nodeTimeout, nil)
// Use a large buffer for CLI to avoid blocking
flowNodeStatusChan := make(chan runner.FlowNodeStatus, 10000)
diff --git a/apps/cli/internal/runner/runner_test.go b/apps/cli/internal/runner/runner_test.go
index 67271a7c2..cd11a0105 100644
--- a/apps/cli/internal/runner/runner_test.go
+++ b/apps/cli/internal/runner/runner_test.go
@@ -26,9 +26,11 @@ import (
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/senv"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sflow"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/shttp"
+ "github.com/the-dev-tools/dev-tools/packages/server/pkg/service/swebsocket"
"github.com/the-dev-tools/dev-tools/packages/server/pkg/service/sworkspace"
yamlflowsimplev2 "github.com/the-dev-tools/dev-tools/packages/server/pkg/translate/yamlflowsimplev2"
"github.com/the-dev-tools/dev-tools/packages/spec/dist/buf/go/api/private/node_js_executor/v1/node_js_executorv1connect"
+ "github.com/coder/websocket"
)
// flowTestFixture provides a common test environment for flow execution tests
@@ -88,6 +90,13 @@ func newFlowTestFixture(t *testing.T) *flowTestFixture {
httpBodyRawService := shttp.NewHttpBodyRawService(queries)
httpAssertService := shttp.NewHttpAssertService(queries)
+ // WebSocket services
+ nodeWsConnectionService := sflow.NewNodeWsConnectionService(queries)
+ nodeWsSendService := sflow.NewNodeWsSendService(queries)
+ nodeWaitService := sflow.NewNodeWaitService(queries)
+ webSocketService := swebsocket.New(queries, logger)
+ webSocketHeaderService := swebsocket.NewWebSocketHeaderService(queries)
+
// Additional services for builder
varService := senv.NewVariableService(queries, logger)
@@ -114,6 +123,11 @@ func newFlowTestFixture(t *testing.T) *flowTestFixture {
nil, // NodeAiProviderService - not needed for CLI tests
nil, // NodeMemoryService - not needed for CLI tests
nil, // NodeGraphQLService - not needed for CLI tests
+ &nodeWsConnectionService,
+ &nodeWsSendService,
+ &nodeWaitService,
+ &webSocketService,
+ &webSocketHeaderService,
nil, // GraphQLService - not needed for CLI tests
nil, // GraphQLHeaderService - not needed for CLI tests
&workspaceService,
@@ -149,6 +163,11 @@ func newFlowTestFixture(t *testing.T) *flowTestFixture {
HTTPBodyUrlEncoded: httpBodyUrlEncodedService,
HTTPBodyRaw: httpBodyRawService,
HTTPAssert: httpAssertService,
+ NodeWsConnection: nodeWsConnectionService,
+ NodeWsSend: nodeWsSendService,
+ NodeWait: nodeWaitService,
+ WebSocket: webSocketService,
+ WebSocketHeader: webSocketHeaderService,
Logger: logger,
}
@@ -763,3 +782,103 @@ flows:
t.Error("OrphanRequest should NOT have been executed (it's an orphan node)")
}
}
+
+// echoWSServer creates a test WebSocket server that echoes messages back.
+func echoWSServer(t *testing.T) *httptest.Server {
+ t.Helper()
+ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ conn, err := websocket.Accept(w, r, nil)
+ if err != nil {
+ return
+ }
+ defer conn.Close(websocket.StatusNormalClosure, "") //nolint:errcheck // best-effort cleanup
+ for {
+ typ, msg, err := conn.Read(r.Context())
+ if err != nil {
+ return
+ }
+ if err := conn.Write(r.Context(), typ, msg); err != nil {
+ return
+ }
+ }
+ }))
+}
+
+func wsURL(s *httptest.Server) string {
+ return "ws" + strings.TrimPrefix(s.URL, "http")
+}
+
+// TestFlowRun_WebSocket tests a flow with WebSocket connection and send nodes.
+func TestFlowRun_WebSocket(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping integration test in short mode")
+ }
+
+ fixture := newFlowTestFixture(t)
+
+ wsSrv := echoWSServer(t)
+ defer wsSrv.Close()
+
+ yamlContent := fmt.Sprintf(`workspace_name: WS Test
+flows:
+ - name: WSFlow
+ steps:
+ - manual_start:
+ name: Start
+ - ws_connection:
+ name: MyWS
+ depends_on: Start
+ url: %s
+ - ws_send:
+ name: SendHello
+ depends_on: MyWS
+ ws_connection_node_name: MyWS
+ message: '{"hello":"world"}'
+`, wsURL(wsSrv))
+
+ resolved, err := yamlflowsimplev2.ConvertSimplifiedYAML([]byte(yamlContent), yamlflowsimplev2.ConvertOptionsV2{
+ WorkspaceID: fixture.workspaceID,
+ })
+ if err != nil {
+ t.Fatalf("failed to convert YAML: %v", err)
+ }
+
+ fixture.importWorkspaceBundle(resolved)
+
+ flow := fixture.getFlowByName("WSFlow")
+ if flow == nil {
+ t.Fatal("WSFlow not found")
+ }
+
+ ctx, cancel := context.WithTimeout(fixture.ctx, 10*time.Second)
+ defer cancel()
+
+ result, err := runner.RunFlow(ctx, flow, fixture.getRunnerServices(nil), nil)
+
+ if err != nil {
+ t.Errorf("flow execution failed: %v", err)
+ }
+
+ if result.Status != "success" {
+ t.Errorf("expected status 'success', got '%s'. Error: %s", result.Status, result.Error)
+ }
+
+ // Verify both WS nodes were executed
+ foundConn := false
+ foundSend := false
+ for _, node := range result.Nodes {
+ switch node.Name {
+ case "MyWS":
+ foundConn = true
+ case "SendHello":
+ foundSend = true
+ }
+ }
+
+ if !foundConn {
+ t.Error("WS connection node 'MyWS' was not executed")
+ }
+ if !foundSend {
+ t.Error("WS send node 'SendHello' was not executed")
+ }
+}
diff --git a/apps/cli/test/yamlflow/integration_yamlflow_test.go b/apps/cli/test/yamlflow/integration_yamlflow_test.go
index 95d09ed26..a53ee1f17 100644
--- a/apps/cli/test/yamlflow/integration_yamlflow_test.go
+++ b/apps/cli/test/yamlflow/integration_yamlflow_test.go
@@ -76,3 +76,4 @@ func TestYAMLFlow_TestRunField(t *testing.T) {
func TestYAMLFlow_GraphQLRun(t *testing.T) {
runCLI(t, "graphql_run_example.yaml")
}
+
diff --git a/apps/cli/test/yamlflow/ws_run_example.yaml b/apps/cli/test/yamlflow/ws_run_example.yaml
new file mode 100644
index 000000000..400c8ce6c
--- /dev/null
+++ b/apps/cli/test/yamlflow/ws_run_example.yaml
@@ -0,0 +1,57 @@
+workspace_name: New Workspace
+run:
+ - flow: ws-integration
+flows:
+ - name: ws-integration
+ steps:
+ - manual_start:
+ name: Start
+ position_x: -182.05
+ position_y: -3.7
+ - wait:
+ name: wait_4
+ depends_on: Start
+ position_x: 201
+ position_y: -139.77
+ duration_ms: '8000'
+ - ws_connection:
+ name: ws_connection_5
+ depends_on: Start
+ position_x: 218.43
+ position_y: -3.21
+ url: http://localhost:8080/
+ - for:
+ name: for_6
+ depends_on: ws_connection_5
+ position_x: 462.56
+ position_y: -146.17
+ iter_count: '10'
+ - wait:
+ name: wait_7
+ depends_on: ws_connection_5
+ position_x: 470.57
+ position_y: 88.8
+ duration_ms: '1000'
+ - ws_send:
+ name: ws_send_6
+ depends_on: for_6.loop
+ position_x: 592.26
+ position_y: -52.7
+ ws_connection_node_name: ws_connection_5
+ message: '{"a":"{{ for_6.index }}"}'
+ - ws_send:
+ name: ws_send_6_1
+ depends_on: wait_7
+ position_x: 631.96
+ position_y: 99.13
+ ws_connection_node_name: ws_connection_5
+ message: '{"a":"2"}'
+ - wait:
+ name: wait_7
+ depends_on: ws_send_6
+ position_x: 803.84
+ position_y: -54.89
+ duration_ms: '1000'
+environments:
+ - name: default
+ variables: {}
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 5e22dcbe5..d617a2a1c 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -37,7 +37,7 @@
"electron": "catalog:",
"electron-builder": "catalog:",
"electron-devtools-installer": "catalog:",
- "electron-updater": "6.7.3",
+ "electron-updater": "catalog:",
"electron-vite": "catalog:",
"eslint": "catalog:",
"react": "catalog:",
diff --git a/apps/desktop/src/renderer/main.tsx b/apps/desktop/src/renderer/main.tsx
index d1fee1941..a79d93a31 100644
--- a/apps/desktop/src/renderer/main.tsx
+++ b/apps/desktop/src/renderer/main.tsx
@@ -68,7 +68,7 @@ const UpdateAvailable = ({ children }: UpdateAvailableProps) => {
Update available!
- {/* eslint-disable-next-line better-tailwindcss/no-unregistered-classes */}
+ {/* eslint-disable-next-line better-tailwindcss/no-unknown-classes */}
{children}
diff --git a/go.work.sum b/go.work.sum
index f04746243..1b3e66c8e 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -25,18 +25,12 @@ cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbD
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
-cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
-cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/accessapproval v1.8.6/go.mod h1:FfmTs7Emex5UvfnnpMkhuNkRCP85URnBFt5ClLxhZaQ=
cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk=
cloud.google.com/go/accesscontextmanager v1.9.6/go.mod h1:884XHwy1AQpCX5Cj2VqYse77gfLaq9f8emE2bYriilk=
cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM=
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
-cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0=
-cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
-cloud.google.com/go/aiplatform v1.89.0 h1:niSJYc6ldWWVM9faXPo1Et1MVSQoLvVGriD7fwbJdtE=
-cloud.google.com/go/aiplatform v1.89.0/go.mod h1:TzZtegPkinfXTtXVvZZpxx7noINFMVDrLkE7cEWhYEk=
cloud.google.com/go/aiplatform v1.109.0/go.mod h1:4rwKOMdubQOND81AlO3EckcskvEFCYSzXKfn42GMm8k=
cloud.google.com/go/alloydb v1.14.0/go.mod h1:OTBY1HoL0Z8PsHoMMVhkaUPKyY8oP7hzIAe/Dna6UHk=
cloud.google.com/go/alloydbconn v1.13.2/go.mod h1:0wlYQAOr2XuvxYsvNNVckmG2v17WVUKzMD+gmTOibSU=
@@ -67,12 +61,8 @@ cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
-cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc=
-cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
-cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
-cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/automl v1.14.7/go.mod h1:8a4XbIH5pdvrReOU72oB+H3pOw2JBxo9XTk39oljObE=
cloud.google.com/go/automl v1.15.0/go.mod h1:U9zOtQb8zVrFNGTuW3BfxeqmLyeleLgT9B12EaXfODg=
cloud.google.com/go/baremetalsolution v1.3.6/go.mod h1:7/CS0LzpLccRGO0HL3q2Rofxas2JwjREKut414sE9iM=
@@ -115,8 +105,6 @@ cloud.google.com/go/compute v1.49.1/go.mod h1:1uoZvP8Avyfhe3Y4he7sMOR16ZiAm2Q+Rc
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
-cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/contactcenterinsights v1.17.3/go.mod h1:7Uu2CpxS3f6XxhRdlEzYAkrChpR5P5QfcdGAFEdHOG8=
cloud.google.com/go/contactcenterinsights v1.17.4/go.mod h1:kZe6yOnKDfpPz2GphDHynxk/Spx+53UX/pGf+SmWAKM=
cloud.google.com/go/container v1.43.0/go.mod h1:ETU9WZ1KM9ikEKLzrhRVao7KHtalDQu6aPqM34zDr/U=
@@ -182,8 +170,6 @@ cloud.google.com/go/gkemulticloud v1.5.4/go.mod h1:7l9+6Tp4jySSGj4PStO8CE6RrHFdc
cloud.google.com/go/grafeas v0.3.15/go.mod h1:irwcwIQOBlLBotGdMwme8PipnloOPqILfIvMwlmu8Pk=
cloud.google.com/go/gsuiteaddons v1.7.7/go.mod h1:zTGmmKG/GEBCONsvMOY2ckDiEsq3FN+lzWGUiXccF9o=
cloud.google.com/go/gsuiteaddons v1.7.8/go.mod h1:DBKNHH4YXAdd/rd6zVvtOGAJNGo0ekOh+nIjTUDEJ5U=
-cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
-cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/iap v1.11.2/go.mod h1:Bh99DMUpP5CitL9lK0BC8MYgjjYO4b3FbyhgW1VHJvg=
cloud.google.com/go/iap v1.11.3/go.mod h1:+gXO0ClH62k2LVlfhHzrpiHQNyINlEVmGAE3+DB4ShU=
@@ -203,8 +189,6 @@ cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
-cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
-cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
cloud.google.com/go/managedidentities v1.7.6/go.mod h1:pYCWPaI1AvR8Q027Vtp+SFSM/VOVgbjBF4rxp1/z5p4=
cloud.google.com/go/managedidentities v1.7.7/go.mod h1:nwNlMxtBo2YJMvsKXRtAD1bL41qiCI9npS7cbqrsJUs=
@@ -472,7 +456,6 @@ github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cristalhq/acmd v0.12.0 h1:RdlKnxjN+txbQosg8p/TRNZ+J1Rdne43MVQZ1zDhGWk=
github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=
@@ -605,8 +588,6 @@ github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76
github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg=
github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
-github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
-github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -646,22 +627,19 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/cloud-bigtable-clients-test v0.0.3/go.mod h1:TWtDzrrAI70C3dNLDY+nZN3gxHtFdZIbpL9rCTFyxE0=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
-github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
-github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
-github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -735,7 +713,6 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
@@ -829,7 +806,6 @@ github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pinecone-io/go-pinecone v0.4.1/go.mod h1:KwWSueZFx9zccC+thBk13+LDiOgii8cff9bliUI4tQs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
@@ -847,7 +823,6 @@ github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71 h1:CN
github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50=
github.com/redis/rueidis v1.0.34/go.mod h1:g8nPmgR4C68N3abFiOc/gUOSEKw3Tom6/teYMehg4RE=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -990,8 +965,6 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
@@ -1253,8 +1226,6 @@ golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
-golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
-golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -1346,8 +1317,6 @@ google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6c
google.golang.org/api v0.237.0 h1:MP7XVsGZesOsx3Q8WVa4sUdbrsTvDSOERd3Vh4xj/wc=
google.golang.org/api v0.237.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
-google.golang.org/api v0.246.0 h1:H0ODDs5PnMZVZAEtdLMn2Ul2eQi7QNjqM2DIFp8TlTM=
-google.golang.org/api v0.246.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1398,8 +1367,6 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
-google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
-google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
@@ -1528,6 +1495,7 @@ modernc.org/scannertest v1.0.2 h1:JPtfxcVdbRvzmRf2YUvsDibJsQRw8vKA/3jb31y7cy0=
modernc.org/scannertest v1.0.2/go.mod h1:RzTm5RwglF/6shsKoEivo8N91nQIoWtcWI7ns+zPyGA=
modernc.org/y v1.1.0 h1:JdIvLry+rKeSsVNRCdr6YWYimwwNm0GXtzxid77VfWc=
modernc.org/y v1.1.0/go.mod h1:Iz3BmyIS4OwAbwGaUS7cqRrLsSsfp2sFWtpzX+P4CsE=
+nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/install.ps1 b/install.ps1
new file mode 100644
index 000000000..c16f16ef1
--- /dev/null
+++ b/install.ps1
@@ -0,0 +1,722 @@
+# Issue Tracker: https://github.com/ScoopInstaller/Install/issues
+# Unlicense License:
+#
+# This is free and unencumbered software released into the public domain.
+#
+# Anyone is free to copy, modify, publish, use, compile, sell, or
+# distribute this software, either in source code form or as a compiled
+# binary, for any purpose, commercial or non-commercial, and by any
+# means.
+#
+# In jurisdictions that recognize copyright laws, the author or authors
+# of this software dedicate any and all copyright interest in the
+# software to the public domain. We make this dedication for the benefit
+# of the public at large and to the detriment of our heirs and
+# successors. We intend this dedication to be an overt act of
+# relinquishment in perpetuity of all present and future rights to this
+# software under copyright law.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# For more information, please refer to
+
+<#
+.SYNOPSIS
+ Scoop installer.
+.DESCRIPTION
+ The installer of Scoop. For details please check the website and wiki.
+.PARAMETER ScoopDir
+ Specifies Scoop root path.
+ If not specified, Scoop will be installed to '$env:USERPROFILE\scoop'.
+.PARAMETER ScoopGlobalDir
+ Specifies directory to store global apps.
+ If not specified, global apps will be installed to '$env:ProgramData\scoop'.
+.PARAMETER ScoopCacheDir
+ Specifies cache directory.
+ If not specified, caches will be downloaded to '$ScoopDir\cache'.
+.PARAMETER NoProxy
+ Bypass system proxy during the installation.
+.PARAMETER Proxy
+ Specifies proxy to use during the installation.
+.PARAMETER ProxyCredential
+ Specifies credential for the given proxy.
+.PARAMETER ProxyUseDefaultCredentials
+ Use the credentials of the current user for the proxy server that is specified by the -Proxy parameter.
+.PARAMETER RunAsAdmin
+ Force to run the installer as administrator.
+.LINK
+ https://scoop.sh
+.LINK
+ https://github.com/ScoopInstaller/Scoop/wiki
+#>
+param(
+ [String] $ScoopDir,
+ [String] $ScoopGlobalDir,
+ [String] $ScoopCacheDir,
+ [Switch] $NoProxy,
+ [Uri] $Proxy,
+ [System.Management.Automation.PSCredential] $ProxyCredential,
+ [Switch] $ProxyUseDefaultCredentials,
+ [Switch] $RunAsAdmin
+)
+
+# Disable StrictMode in this script
+Set-StrictMode -Off
+
+function Write-InstallInfo {
+ param(
+ [Parameter(Mandatory = $True, Position = 0)]
+ [String] $String,
+ [Parameter(Mandatory = $False, Position = 1)]
+ [System.ConsoleColor] $ForegroundColor = $host.UI.RawUI.ForegroundColor
+ )
+
+ $backup = $host.UI.RawUI.ForegroundColor
+
+ if ($ForegroundColor -ne $host.UI.RawUI.ForegroundColor) {
+ $host.UI.RawUI.ForegroundColor = $ForegroundColor
+ }
+
+ Write-Output "$String"
+
+ $host.UI.RawUI.ForegroundColor = $backup
+}
+
+function Exit-Install {
+ param(
+ [Int] $ErrorCode = 1
+ )
+
+ if ($IS_EXECUTED_FROM_IEX) {
+ # Don't abort with `exit` that would close the PS session if invoked
+ # with iex, yet set `LASTEXITCODE` for the caller to check
+ $Global:LASTEXITCODE = $ErrorCode
+ break
+ } else {
+ exit $ErrorCode
+ }
+}
+
+function Deny-Install {
+ param(
+ [String] $Message,
+ [Int] $ErrorCode = 1
+ )
+
+ Write-InstallInfo -String $Message -ForegroundColor DarkRed
+ Write-InstallInfo 'Abort.'
+ Exit-Install -ErrorCode $ErrorCode
+}
+
+function Test-LanguageMode {
+ if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') {
+ # `Write-InstallInfo` cannot be used here as it depends on FullLanguage mode
+ Write-Output 'Scoop requires PowerShell FullLanguage mode to run, current PowerShell environment is restricted.'
+ Write-Output 'Abort.'
+ Exit-Install
+ }
+}
+
+function Test-ValidateParameter {
+ if ($null -eq $Proxy -and ($null -ne $ProxyCredential -or $ProxyUseDefaultCredentials)) {
+ Deny-Install 'Provide a valid proxy URI for the -Proxy parameter when using the -ProxyCredential or -ProxyUseDefaultCredentials.'
+ }
+
+ if ($ProxyUseDefaultCredentials -and $null -ne $ProxyCredential) {
+ Deny-Install "ProxyUseDefaultCredentials is conflict with ProxyCredential. Don't use the -ProxyCredential and -ProxyUseDefaultCredentials together."
+ }
+}
+
+function Test-IsAdministrator {
+ return ([Security.Principal.WindowsPrincipal]`
+ [Security.Principal.WindowsIdentity]::GetCurrent()`
+ ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+function Test-Prerequisite {
+ # Scoop requires PowerShell 5 at least
+ if (($PSVersionTable.PSVersion.Major) -lt 5) {
+ Deny-Install 'PowerShell 5 or later is required to run Scoop. Go to https://microsoft.com/powershell to get the latest version of PowerShell.'
+ }
+
+ # Scoop requires TLS 1.2 SecurityProtocol, which exists in .NET Framework 4.5+
+ if ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -notcontains 'Tls12') {
+ Deny-Install 'Scoop requires .NET Framework 4.5+ to work. Go to https://microsoft.com/net/download to get the latest version of .NET Framework.'
+ }
+
+ # Ensure Robocopy.exe is accessible
+ if (!(Test-CommandAvailable('robocopy'))) {
+ Deny-Install "Scoop requires 'C:\Windows\System32\Robocopy.exe' to work. Please make sure 'C:\Windows\System32' is in your PATH."
+ }
+
+ # Detect if RunAsAdministrator, there is no need to run as administrator when installing Scoop
+ if (!$RunAsAdmin -and (Test-IsAdministrator)) {
+ # Exception: Windows Sandbox, GitHub Actions CI
+ $exception = ($env:USERNAME -eq 'WDAGUtilityAccount') -or ($env:GITHUB_ACTIONS -eq 'true' -and $env:CI -eq 'true')
+ if (!$exception) {
+ Deny-Install 'Running the installer as administrator is disabled by default, see https://github.com/ScoopInstaller/Install#for-admin for details.'
+ }
+ }
+
+ # Show notification to change execution policy
+ $allowedExecutionPolicy = @('Unrestricted', 'RemoteSigned', 'ByPass')
+ if ((Get-ExecutionPolicy).ToString() -notin $allowedExecutionPolicy) {
+ Deny-Install "PowerShell requires an execution policy in [$($allowedExecutionPolicy -join ', ')] to run Scoop. For example, to set the execution policy to 'RemoteSigned' please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser'."
+ }
+
+ # Test if scoop is installed, by checking if scoop command exists.
+ if (Test-CommandAvailable('scoop')) {
+ Deny-Install "Scoop is already installed. Run 'scoop update' to get the latest version." -ErrorCode 0
+ }
+}
+
+function Optimize-SecurityProtocol {
+ # .NET Framework 4.7+ has a default security protocol called 'SystemDefault',
+ # which allows the operating system to choose the best protocol to use.
+ # If SecurityProtocolType contains 'SystemDefault' (means .NET4.7+ detected)
+ # and the value of SecurityProtocol is 'SystemDefault', just do nothing on SecurityProtocol,
+ # 'SystemDefault' will use TLS 1.2 if the webrequest requires.
+ $isNewerNetFramework = ([System.Enum]::GetNames([System.Net.SecurityProtocolType]) -contains 'SystemDefault')
+ $isSystemDefault = ([System.Net.ServicePointManager]::SecurityProtocol.Equals([System.Net.SecurityProtocolType]::SystemDefault))
+
+ # If not, change it to support TLS 1.2
+ if (!($isNewerNetFramework -and $isSystemDefault)) {
+ # Set to TLS 1.2 (3072), then TLS 1.1 (768), and TLS 1.0 (192). Ssl3 has been superseded,
+ # https://docs.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=netframework-4.5
+ [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192
+ Write-Verbose 'SecurityProtocol has been updated to support TLS 1.2'
+ }
+}
+
+function Get-Downloader {
+ $downloadSession = New-Object System.Net.WebClient
+
+ # Set proxy to null if NoProxy is specified
+ if ($NoProxy) {
+ $downloadSession.Proxy = $null
+ } elseif ($Proxy) {
+ # Prepend protocol if not provided
+ if (!$Proxy.IsAbsoluteUri) {
+ $Proxy = New-Object System.Uri('http://' + $Proxy.OriginalString)
+ }
+
+ $Proxy = New-Object System.Net.WebProxy($Proxy)
+
+ if ($null -ne $ProxyCredential) {
+ $Proxy.Credentials = $ProxyCredential.GetNetworkCredential()
+ } elseif ($ProxyUseDefaultCredentials) {
+ $Proxy.UseDefaultCredentials = $true
+ }
+
+ $downloadSession.Proxy = $Proxy
+ }
+
+ return $downloadSession
+}
+
+function Test-isFileLocked {
+ param(
+ [String] $path
+ )
+
+ $file = New-Object System.IO.FileInfo $path
+
+ if (!(Test-Path $path)) {
+ return $false
+ }
+
+ try {
+ $stream = $file.Open(
+ [System.IO.FileMode]::Open,
+ [System.IO.FileAccess]::ReadWrite,
+ [System.IO.FileShare]::None
+ )
+ if ($stream) {
+ $stream.Close()
+ }
+ return $false
+ } catch {
+ # The file is locked by a process.
+ return $true
+ }
+}
+
+function Expand-ZipArchive {
+ param(
+ [String] $path,
+ [String] $to
+ )
+
+ if (!(Test-Path $path)) {
+ Deny-Install "Unzip failed: can't find $path to unzip."
+ }
+
+ # Check if the zip file is locked, by antivirus software for example
+ $retries = 0
+ while ($retries -le 10) {
+ if ($retries -eq 10) {
+ Deny-Install "Unzip failed: can't unzip because a process is locking the file."
+ }
+ if (Test-isFileLocked $path) {
+ Write-InstallInfo "Waiting for $path to be unlocked by another process... ($retries/10)"
+ $retries++
+ Start-Sleep -Seconds 2
+ } else {
+ break
+ }
+ }
+
+ # Workaround to suspend Expand-Archive verbose output,
+ # upstream issue: https://github.com/PowerShell/Microsoft.PowerShell.Archive/issues/98
+ $oldVerbosePreference = $VerbosePreference
+ $global:VerbosePreference = 'SilentlyContinue'
+
+ # Disable progress bar to gain performance
+ $oldProgressPreference = $ProgressPreference
+ $global:ProgressPreference = 'SilentlyContinue'
+
+ # PowerShell 5+: use Expand-Archive to extract zip files
+ Microsoft.PowerShell.Archive\Expand-Archive -Path $path -DestinationPath $to -Force
+ $global:VerbosePreference = $oldVerbosePreference
+ $global:ProgressPreference = $oldProgressPreference
+}
+
+function Out-UTF8File {
+ param(
+ [Parameter(Mandatory = $True, Position = 0)]
+ [Alias('Path')]
+ [String] $FilePath,
+ [Switch] $Append,
+ [Switch] $NoNewLine,
+ [Parameter(ValueFromPipeline = $True)]
+ [PSObject] $InputObject
+ )
+ process {
+ if ($Append) {
+ [System.IO.File]::AppendAllText($FilePath, $InputObject)
+ } else {
+ if (!$NoNewLine) {
+ # Ref: https://stackoverflow.com/questions/5596982
+ # Performance Note: `WriteAllLines` throttles memory usage while
+ # `WriteAllText` needs to keep the complete string in memory.
+ [System.IO.File]::WriteAllLines($FilePath, $InputObject)
+ } else {
+ # However `WriteAllText` does not add ending newline.
+ [System.IO.File]::WriteAllText($FilePath, $InputObject)
+ }
+ }
+ }
+}
+
+function Import-ScoopShim {
+ Write-InstallInfo 'Creating shim...'
+ # The scoop executable
+ $path = "$SCOOP_APP_DIR\bin\scoop.ps1"
+
+ if (!(Test-Path $SCOOP_SHIMS_DIR)) {
+ New-Item -Type Directory $SCOOP_SHIMS_DIR | Out-Null
+ }
+
+ # The scoop shim
+ $shim = "$SCOOP_SHIMS_DIR\scoop"
+
+ # Convert to relative path
+ Push-Location $SCOOP_SHIMS_DIR
+ $relativePath = Resolve-Path -Relative $path
+ Pop-Location
+ $absolutePath = Resolve-Path $path
+
+ # if $path points to another drive resolve-path prepends .\ which could break shims
+ $ps1text = if ($relativePath -match '^(\.\\)?\w:.*$') {
+ @(
+ "# $absolutePath",
+ "`$path = `"$path`"",
+ "if (`$MyInvocation.ExpectingInput) { `$input | & `$path $arg @args } else { & `$path $arg @args }",
+ "exit `$LASTEXITCODE"
+ )
+ } else {
+ @(
+ "# $absolutePath",
+ "`$path = Join-Path `$PSScriptRoot `"$relativePath`"",
+ "if (`$MyInvocation.ExpectingInput) { `$input | & `$path $arg @args } else { & `$path $arg @args }",
+ "exit `$LASTEXITCODE"
+ )
+ }
+ $ps1text -join "`r`n" | Out-UTF8File "$shim.ps1"
+
+ # make ps1 accessible from cmd.exe
+ @(
+ "@rem $absolutePath",
+ '@echo off',
+ 'setlocal enabledelayedexpansion',
+ 'set args=%*',
+ ':: replace problem characters in arguments',
+ "set args=%args:`"='%",
+ "set args=%args:(=``(%",
+ "set args=%args:)=``)%",
+ "set invalid=`"='",
+ 'if !args! == !invalid! ( set args= )',
+ 'where /q pwsh.exe',
+ 'if %errorlevel% equ 0 (',
+ " pwsh -noprofile -ex unrestricted -file `"$absolutePath`" $arg %args%",
+ ') else (',
+ " powershell -noprofile -ex unrestricted -file `"$absolutePath`" $arg %args%",
+ ')'
+ ) -join "`r`n" | Out-UTF8File "$shim.cmd"
+
+ @(
+ '#!/bin/sh',
+ "# $absolutePath",
+ 'if command -v pwsh.exe > /dev/null 2>&1; then',
+ " pwsh.exe -noprofile -ex unrestricted -file `"$absolutePath`" $arg `"$@`"",
+ 'else',
+ " powershell.exe -noprofile -ex unrestricted -file `"$absolutePath`" $arg `"$@`"",
+ 'fi'
+ ) -join "`n" | Out-UTF8File $shim -NoNewLine
+}
+
+function Get-Env {
+ param(
+ [String] $name,
+ [Switch] $global
+ )
+
+ $RegisterKey = if ($global) {
+ Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
+ } else {
+ Get-Item -Path 'HKCU:'
+ }
+
+ $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment')
+ $RegistryValueOption = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
+ $EnvRegisterKey.GetValue($name, $null, $RegistryValueOption)
+}
+
+function Publish-Env {
+ if (-not ('Win32.NativeMethods' -as [Type])) {
+ Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @'
+[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+public static extern IntPtr SendMessageTimeout(
+ IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
+ uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
+'@
+ }
+
+ $HWND_BROADCAST = [IntPtr] 0xffff
+ $WM_SETTINGCHANGE = 0x1a
+ $result = [UIntPtr]::Zero
+
+ [Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST,
+ $WM_SETTINGCHANGE,
+ [UIntPtr]::Zero,
+ 'Environment',
+ 2,
+ 5000,
+ [ref] $result
+ ) | Out-Null
+}
+
+function Write-Env {
+ param(
+ [String] $name,
+ [String] $val,
+ [Switch] $global
+ )
+
+ $RegisterKey = if ($global) {
+ Get-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager'
+ } else {
+ Get-Item -Path 'HKCU:'
+ }
+
+ $EnvRegisterKey = $RegisterKey.OpenSubKey('Environment', $true)
+ if ($val -eq $null) {
+ $EnvRegisterKey.DeleteValue($name)
+ } else {
+ $RegistryValueKind = if ($val.Contains('%')) {
+ [Microsoft.Win32.RegistryValueKind]::ExpandString
+ } elseif ($EnvRegisterKey.GetValue($name)) {
+ $EnvRegisterKey.GetValueKind($name)
+ } else {
+ [Microsoft.Win32.RegistryValueKind]::String
+ }
+ $EnvRegisterKey.SetValue($name, $val, $RegistryValueKind)
+ }
+ Publish-Env
+}
+
+function Add-ShimsDirToPath {
+ # Get $env:PATH of current user
+ $userEnvPath = Get-Env 'PATH'
+
+ if ($userEnvPath -notmatch [Regex]::Escape($SCOOP_SHIMS_DIR)) {
+ $h = (Get-PSProvider 'FileSystem').Home
+ if (!$h.EndsWith('\')) {
+ $h += '\'
+ }
+
+ if (!($h -eq '\')) {
+ $friendlyPath = "$SCOOP_SHIMS_DIR" -replace ([Regex]::Escape($h)), '~\'
+ Write-InstallInfo "Adding $friendlyPath to your path."
+ } else {
+ Write-InstallInfo "Adding $SCOOP_SHIMS_DIR to your path."
+ }
+
+ # For future sessions
+ Write-Env 'PATH' "$SCOOP_SHIMS_DIR;$userEnvPath"
+ # For current session
+ $env:PATH = "$SCOOP_SHIMS_DIR;$env:PATH"
+ }
+}
+
+function Use-Config {
+ if (!(Test-Path $SCOOP_CONFIG_FILE)) {
+ return $null
+ }
+
+ try {
+ return (Get-Content $SCOOP_CONFIG_FILE -Raw | ConvertFrom-Json -ErrorAction Stop)
+ } catch {
+ Deny-Install "ERROR loading $SCOOP_CONFIG_FILE`: $($_.Exception.Message)"
+ }
+}
+
+function Add-Config {
+ param (
+ [Parameter(Mandatory = $True, Position = 0)]
+ [String] $Name,
+ [Parameter(Mandatory = $True, Position = 1)]
+ [String] $Value
+ )
+
+ $scoopConfig = Use-Config
+
+ if ($scoopConfig -is [System.Management.Automation.PSObject]) {
+ if ($Value -eq [bool]::TrueString -or $Value -eq [bool]::FalseString) {
+ $Value = [System.Convert]::ToBoolean($Value)
+ }
+ if ($null -eq $scoopConfig.$Name) {
+ $scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value
+ } else {
+ $scoopConfig.$Name = $Value
+ }
+ } else {
+ $baseDir = Split-Path -Path $SCOOP_CONFIG_FILE
+ if (!(Test-Path $baseDir)) {
+ New-Item -Type Directory $baseDir | Out-Null
+ }
+
+ $scoopConfig = New-Object PSObject
+ $scoopConfig | Add-Member -MemberType NoteProperty -Name $Name -Value $Value
+ }
+
+ if ($null -eq $Value) {
+ $scoopConfig.PSObject.Properties.Remove($Name)
+ }
+
+ ConvertTo-Json $scoopConfig | Set-Content $SCOOP_CONFIG_FILE -Encoding ASCII
+ return $scoopConfig
+}
+
+function Add-DefaultConfig {
+ # If user-level SCOOP env not defined, save to root_path
+ if (!(Get-Env 'SCOOP')) {
+ if ($SCOOP_DIR -ne "$env:USERPROFILE\scoop") {
+ Write-Verbose "Adding config root_path: $SCOOP_DIR"
+ Add-Config -Name 'root_path' -Value $SCOOP_DIR | Out-Null
+ }
+ }
+
+ # Use system SCOOP_GLOBAL, or set system SCOOP_GLOBAL
+ # with $env:SCOOP_GLOBAL if RunAsAdmin, otherwise save to global_path
+ if (!(Get-Env 'SCOOP_GLOBAL' -global)) {
+ if ((Test-IsAdministrator) -and $env:SCOOP_GLOBAL) {
+ Write-Verbose "Setting System Environment Variable SCOOP_GLOBAL: $env:SCOOP_GLOBAL"
+ [Environment]::SetEnvironmentVariable('SCOOP_GLOBAL', $env:SCOOP_GLOBAL, 'Machine')
+ } else {
+ if ($SCOOP_GLOBAL_DIR -ne "$env:ProgramData\scoop") {
+ Write-Verbose "Adding config global_path: $SCOOP_GLOBAL_DIR"
+ Add-Config -Name 'global_path' -Value $SCOOP_GLOBAL_DIR | Out-Null
+ }
+ }
+ }
+
+ # Use system SCOOP_CACHE, or set system SCOOP_CACHE
+ # with $env:SCOOP_CACHE if RunAsAdmin, otherwise save to cache_path
+ if (!(Get-Env 'SCOOP_CACHE' -global)) {
+ if ((Test-IsAdministrator) -and $env:SCOOP_CACHE) {
+ Write-Verbose "Setting System Environment Variable SCOOP_CACHE: $env:SCOOP_CACHE"
+ [Environment]::SetEnvironmentVariable('SCOOP_CACHE', $env:SCOOP_CACHE, 'Machine')
+ } else {
+ if ($SCOOP_CACHE_DIR -ne "$SCOOP_DIR\cache") {
+ Write-Verbose "Adding config cache_path: $SCOOP_CACHE_DIR"
+ Add-Config -Name 'cache_path' -Value $SCOOP_CACHE_DIR | Out-Null
+ }
+ }
+ }
+
+ # save current datetime to last_update
+ Add-Config -Name 'last_update' -Value ([System.DateTime]::Now.ToString('o')) | Out-Null
+}
+
+function Test-CommandAvailable {
+ param (
+ [Parameter(Mandatory = $True, Position = 0)]
+ [String] $Command
+ )
+ return [Boolean](Get-Command $Command -ErrorAction SilentlyContinue)
+}
+
+function Install-Scoop {
+ Write-InstallInfo 'Initializing...'
+ # Validate install parameters
+ Test-ValidateParameter
+ # Check prerequisites
+ Test-Prerequisite
+ # Enable TLS 1.2
+ Optimize-SecurityProtocol
+
+ # Download scoop from GitHub
+ Write-InstallInfo 'Downloading...'
+ $downloader = Get-Downloader
+ [bool]$downloadZipsRequired = $True
+
+ if (Test-CommandAvailable('git')) {
+ $old_https = $env:HTTPS_PROXY
+ $old_http = $env:HTTP_PROXY
+ try {
+ if ($downloader.Proxy) {
+ #define env vars for git when behind a proxy
+ $Env:HTTP_PROXY = $downloader.Proxy.Address
+ $Env:HTTPS_PROXY = $downloader.Proxy.Address
+ }
+ Write-Verbose "Cloning $SCOOP_PACKAGE_GIT_REPO to $SCOOP_APP_DIR"
+ git clone -q $SCOOP_PACKAGE_GIT_REPO $SCOOP_APP_DIR
+ if (-not $?) {
+ throw 'Cloning failed. Falling back to downloading zip files.'
+ }
+ Write-Verbose "Cloning $SCOOP_MAIN_BUCKET_GIT_REPO to $SCOOP_MAIN_BUCKET_DIR"
+ git clone -q $SCOOP_MAIN_BUCKET_GIT_REPO $SCOOP_MAIN_BUCKET_DIR
+ if (-not $?) {
+ throw 'Cloning failed. Falling back to downloading zip files.'
+ }
+ $downloadZipsRequired = $False
+ } catch {
+ Write-Warning "$($_.Exception.Message)"
+ $Global:LASTEXITCODE = 0
+ } finally {
+ $env:HTTPS_PROXY = $old_https
+ $env:HTTP_PROXY = $old_http
+ }
+ }
+
+ if ($downloadZipsRequired) {
+ # 1. download scoop
+ $scoopZipfile = "$SCOOP_APP_DIR\scoop.zip"
+ if (!(Test-Path $SCOOP_APP_DIR)) {
+ New-Item -Type Directory $SCOOP_APP_DIR | Out-Null
+ }
+ Write-Verbose "Downloading $SCOOP_PACKAGE_REPO to $scoopZipfile"
+ $downloader.downloadFile($SCOOP_PACKAGE_REPO, $scoopZipfile)
+ # 2. download scoop main bucket
+ $scoopMainZipfile = "$SCOOP_MAIN_BUCKET_DIR\scoop-main.zip"
+ if (!(Test-Path $SCOOP_MAIN_BUCKET_DIR)) {
+ New-Item -Type Directory $SCOOP_MAIN_BUCKET_DIR | Out-Null
+ }
+ Write-Verbose "Downloading $SCOOP_MAIN_BUCKET_REPO to $scoopMainZipfile"
+ $downloader.downloadFile($SCOOP_MAIN_BUCKET_REPO, $scoopMainZipfile)
+
+ # Extract files from downloaded zip
+ Write-InstallInfo 'Extracting...'
+ # 1. extract scoop
+ $scoopUnzipTempDir = "$SCOOP_APP_DIR\_tmp"
+ Write-Verbose "Extracting $scoopZipfile to $scoopUnzipTempDir"
+ Expand-ZipArchive $scoopZipfile $scoopUnzipTempDir
+ Copy-Item "$scoopUnzipTempDir\scoop-*\*" $SCOOP_APP_DIR -Recurse -Force
+ # 2. extract scoop main bucket
+ $scoopMainUnzipTempDir = "$SCOOP_MAIN_BUCKET_DIR\_tmp"
+ Write-Verbose "Extracting $scoopMainZipfile to $scoopMainUnzipTempDir"
+ Expand-ZipArchive $scoopMainZipfile $scoopMainUnzipTempDir
+ Copy-Item "$scoopMainUnzipTempDir\Main-*\*" $SCOOP_MAIN_BUCKET_DIR -Recurse -Force
+
+ # Cleanup
+ Remove-Item $scoopUnzipTempDir -Recurse -Force
+ Remove-Item $scoopZipfile
+ Remove-Item $scoopMainUnzipTempDir -Recurse -Force
+ Remove-Item $scoopMainZipfile
+ }
+ # Create the scoop shim
+ Import-ScoopShim
+ # Ensure scoop shims is in the PATH
+ Add-ShimsDirToPath
+ # Setup initial configuration of Scoop
+ Add-DefaultConfig
+
+ Write-InstallInfo 'Scoop was installed successfully!' -ForegroundColor DarkGreen
+ Write-InstallInfo "Type 'scoop help' for instructions."
+}
+
+function Write-DebugInfo {
+ param($BoundArgs)
+
+ Write-Verbose '-------- PSBoundParameters --------'
+ $BoundArgs.GetEnumerator() | ForEach-Object { Write-Verbose $_ }
+ Write-Verbose '-------- Environment Variables --------'
+ Write-Verbose "`$env:USERPROFILE: $env:USERPROFILE"
+ Write-Verbose "`$env:ProgramData: $env:ProgramData"
+ Write-Verbose "`$env:SCOOP: $env:SCOOP"
+ Write-Verbose "`$env:SCOOP_CACHE: $SCOOP_CACHE"
+ Write-Verbose "`$env:SCOOP_GLOBAL: $env:SCOOP_GLOBAL"
+ Write-Verbose '-------- Selected Variables --------'
+ Write-Verbose "SCOOP_DIR: $SCOOP_DIR"
+ Write-Verbose "SCOOP_CACHE_DIR: $SCOOP_CACHE_DIR"
+ Write-Verbose "SCOOP_GLOBAL_DIR: $SCOOP_GLOBAL_DIR"
+ Write-Verbose "SCOOP_CONFIG_HOME: $SCOOP_CONFIG_HOME"
+}
+
+# Prepare variables
+$IS_EXECUTED_FROM_IEX = ($null -eq $MyInvocation.MyCommand.Path)
+
+# Abort when the language mode is restricted
+Test-LanguageMode
+
+# Scoop root directory
+$SCOOP_DIR = $ScoopDir, $env:SCOOP, "$env:USERPROFILE\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
+# Scoop global apps directory
+$SCOOP_GLOBAL_DIR = $ScoopGlobalDir, $env:SCOOP_GLOBAL, "$env:ProgramData\scoop" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
+# Scoop cache directory
+$SCOOP_CACHE_DIR = $ScoopCacheDir, $env:SCOOP_CACHE, "$SCOOP_DIR\cache" | Where-Object { -not [String]::IsNullOrEmpty($_) } | Select-Object -First 1
+# Scoop shims directory
+$SCOOP_SHIMS_DIR = "$SCOOP_DIR\shims"
+# Scoop itself directory
+$SCOOP_APP_DIR = "$SCOOP_DIR\apps\scoop\current"
+# Scoop main bucket directory
+$SCOOP_MAIN_BUCKET_DIR = "$SCOOP_DIR\buckets\main"
+# Scoop config file location
+$SCOOP_CONFIG_HOME = $env:XDG_CONFIG_HOME, "$env:USERPROFILE\.config" | Select-Object -First 1
+$SCOOP_CONFIG_FILE = "$SCOOP_CONFIG_HOME\scoop\config.json"
+
+# TODO: Use a specific version of Scoop and the main bucket
+$SCOOP_PACKAGE_REPO = 'https://github.com/ScoopInstaller/Scoop/archive/master.zip'
+$SCOOP_MAIN_BUCKET_REPO = 'https://github.com/ScoopInstaller/Main/archive/master.zip'
+
+$SCOOP_PACKAGE_GIT_REPO = 'https://github.com/ScoopInstaller/Scoop.git'
+$SCOOP_MAIN_BUCKET_GIT_REPO = 'https://github.com/ScoopInstaller/Main.git'
+
+# Quit if anything goes wrong
+$oldErrorActionPreference = $ErrorActionPreference
+$ErrorActionPreference = 'Stop'
+
+# Logging debug info
+Write-DebugInfo $PSBoundParameters
+# Bootstrap function
+Install-Scoop
+
+# Reset $ErrorActionPreference to original value
+$ErrorActionPreference = $oldErrorActionPreference
diff --git a/packages/client/src/app/router/route-tree.gen.ts b/packages/client/src/app/router/route-tree.gen.ts
index 6cd100d81..5d073f254 100644
--- a/packages/client/src/app/router/route-tree.gen.ts
+++ b/packages/client/src/app/router/route-tree.gen.ts
@@ -14,9 +14,11 @@ import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDot
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotUserRoutesSignInRouteImport } from './../../pages/user/routes/signIn'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteImport } from './../../pages/workspace/routes/workspace/$workspaceIdCan/route'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRouteImport } from './../../pages/workspace/routes/workspace/$workspaceIdCan/index'
+import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport } from './../../pages/websocket/routes/websocket/$websocketIdCan/route'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteImport } from './../../pages/http/routes/http/$httpIdCan/route'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteImport } from './../../pages/graphql/routes/graphql/$graphqlIdCan/route'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteImport } from './../../pages/flow/routes/flow/$flowIdCan/route'
+import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport } from './../../pages/websocket/routes/websocket/$websocketIdCan/index'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRouteImport } from './../../pages/http/routes/http/$httpIdCan/index'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRouteImport } from './../../pages/graphql/routes/graphql/$graphqlIdCan/index'
import { Route as dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRouteImport } from './../../pages/flow/routes/flow/$flowIdCan/index'
@@ -64,6 +66,15 @@ const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspace
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute,
} as any,
)
+const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute =
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport.update(
+ {
+ id: '/(websocket)/websocket/$websocketIdCan',
+ path: '/websocket/$websocketIdCan',
+ getParentRoute: () =>
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute,
+ } as any,
+ )
const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute =
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteImport.update(
{
@@ -91,6 +102,15 @@ const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoute
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute,
} as any,
)
+const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute =
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport.update(
+ {
+ id: '/',
+ path: '/',
+ getParentRoute: () =>
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute,
+ } as any,
+ )
const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute =
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRouteImport.update(
{
@@ -164,11 +184,13 @@ export interface FileRoutesByFullPath {
'/workspace/$workspaceIdCan/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren
'/workspace/$workspaceIdCan/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren
'/workspace/$workspaceIdCan/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren
+ '/workspace/$workspaceIdCan/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren
'/workspace/$workspaceIdCan/flow/$flowIdCan/history': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute
- '/workspace/$workspaceIdCan/credential/$credentialIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute
+ '/workspace/$workspaceIdCan/credential/$credentialIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute
'/workspace/$workspaceIdCan/flow/$flowIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute
'/workspace/$workspaceIdCan/graphql/$graphqlIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute
'/workspace/$workspaceIdCan/http/$httpIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute
+ '/workspace/$workspaceIdCan/websocket/$websocketIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute
'/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute
'/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute
}
@@ -182,6 +204,7 @@ export interface FileRoutesByTo {
'/workspace/$workspaceIdCan/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute
'/workspace/$workspaceIdCan/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute
'/workspace/$workspaceIdCan/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute
+ '/workspace/$workspaceIdCan/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute
'/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute
'/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute
}
@@ -195,11 +218,13 @@ export interface FileRoutesById {
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren
+ '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanHistoryRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanIndexRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanIndexRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanIndexRoute
+ '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanDeltaDotdeltaGraphqlIdCanRoute
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan': typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanDeltaDotdeltaHttpIdCanRoute
}
@@ -214,11 +239,13 @@ export interface FileRouteTypes {
| '/workspace/$workspaceIdCan/flow/$flowIdCan'
| '/workspace/$workspaceIdCan/graphql/$graphqlIdCan'
| '/workspace/$workspaceIdCan/http/$httpIdCan'
+ | '/workspace/$workspaceIdCan/websocket/$websocketIdCan'
| '/workspace/$workspaceIdCan/flow/$flowIdCan/history'
- | '/workspace/$workspaceIdCan/credential/$credentialIdCan'
+ | '/workspace/$workspaceIdCan/credential/$credentialIdCan/'
| '/workspace/$workspaceIdCan/flow/$flowIdCan/'
| '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/'
| '/workspace/$workspaceIdCan/http/$httpIdCan/'
+ | '/workspace/$workspaceIdCan/websocket/$websocketIdCan/'
| '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan'
| '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan'
fileRoutesByTo: FileRoutesByTo
@@ -232,6 +259,7 @@ export interface FileRouteTypes {
| '/workspace/$workspaceIdCan/flow/$flowIdCan'
| '/workspace/$workspaceIdCan/graphql/$graphqlIdCan'
| '/workspace/$workspaceIdCan/http/$httpIdCan'
+ | '/workspace/$workspaceIdCan/websocket/$websocketIdCan'
| '/workspace/$workspaceIdCan/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan'
| '/workspace/$workspaceIdCan/http/$httpIdCan/delta/$deltaHttpIdCan'
id:
@@ -244,11 +272,13 @@ export interface FileRouteTypes {
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan'
+ | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/history'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(flow)/flow/$flowIdCan/'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/'
+ | '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(graphql)/graphql/$graphqlIdCan/delta/$deltaGraphqlIdCan'
| '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/delta/$deltaHttpIdCan'
fileRoutesById: FileRoutesById
@@ -297,6 +327,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRouteImport
parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute
}
+ '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan': {
+ id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan'
+ path: '/websocket/$websocketIdCan'
+ fullPath: '/workspace/$workspaceIdCan/websocket/$websocketIdCan'
+ preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteImport
+ parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute
+ }
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan': {
id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan'
path: '/http/$httpIdCan'
@@ -318,6 +355,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteImport
parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute
}
+ '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/': {
+ id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(websocket)/websocket/$websocketIdCan/'
+ path: '/'
+ fullPath: '/workspace/$workspaceIdCan/websocket/$websocketIdCan/'
+ preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRouteImport
+ parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute
+ }
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/': {
id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(http)/http/$httpIdCan/'
path: '/'
@@ -342,7 +386,7 @@ declare module '@tanstack/react-router' {
'/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/': {
id: '/(dashboard)/(workspace)/workspace/$workspaceIdCan/(credential)/credential/$credentialIdCan/'
path: '/credential/$credentialIdCan'
- fullPath: '/workspace/$workspaceIdCan/credential/$credentialIdCan'
+ fullPath: '/workspace/$workspaceIdCan/credential/$credentialIdCan/'
preLoaderRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRouteImport
parentRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRoute
}
@@ -424,11 +468,27 @@ const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoute
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteChildren,
)
+interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren {
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute
+}
+
+const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren: dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren =
+ {
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute:
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanIndexRoute,
+ }
+
+const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren =
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute._addFileChildren(
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteChildren,
+ )
+
interface dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanRouteRouteChildren {
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspaceRoutesWorkspaceWorkspaceIdCanIndexRoute
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotFlowRoutesFlowFlowIdCanRouteRouteWithChildren
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute: typeof dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute
}
@@ -442,6 +502,8 @@ const dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWorkspace
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotGraphqlRoutesGraphqlGraphqlIdCanRouteRouteWithChildren,
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRoute:
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotHttpRoutesHttpHttpIdCanRouteRouteWithChildren,
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRoute:
+ dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotWebsocketRoutesWebsocketWebsocketIdCanRouteRouteWithChildren,
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute:
dashboardDotDotDotDotDotDotDotDotPagesDashboardRoutesDotDotDotDotCredentialRoutesCredentialCredentialIdCanIndexRoute,
}
diff --git a/packages/client/src/features/file-system/index.tsx b/packages/client/src/features/file-system/index.tsx
index 72bb20001..0b900f915 100644
--- a/packages/client/src/features/file-system/index.tsx
+++ b/packages/client/src/features/file-system/index.tsx
@@ -15,7 +15,7 @@ import {
TreeProps,
useDragAndDrop,
} from 'react-aria-components';
-import { FiFolder, FiMoreHorizontal, FiX } from 'react-icons/fi';
+import { FiFolder, FiMoreHorizontal, FiWifi, FiX } from 'react-icons/fi';
import { RiAnthropicFill, RiGeminiFill, RiOpenaiFill } from 'react-icons/ri';
import { TbGauge } from 'react-icons/tb';
import { twJoin } from 'tailwind-merge';
@@ -29,8 +29,12 @@ import {
FolderSchema,
} from '@the-dev-tools/spec/buf/api/file_system/v1/file_system_pb';
import { FlowSchema, FlowService } from '@the-dev-tools/spec/buf/api/flow/v1/flow_pb';
-import { GraphQLSchema as GraphQLItemSchema } from '@the-dev-tools/spec/buf/api/graph_q_l/v1/graph_q_l_pb';
+import {
+ GraphQLDeltaSchema,
+ GraphQLSchema as GraphQLItemSchema,
+} from '@the-dev-tools/spec/buf/api/graph_q_l/v1/graph_q_l_pb';
import { HttpDeltaSchema, HttpMethod, HttpSchema, HttpService } from '@the-dev-tools/spec/buf/api/http/v1/http_pb';
+import { WebSocketSchema as WebSocketItemSchema } from '@the-dev-tools/spec/buf/api/web_socket/v1/web_socket_pb';
import {
CredentialAnthropicCollectionSchema,
CredentialCollectionSchema,
@@ -39,8 +43,9 @@ import {
} from '@the-dev-tools/spec/tanstack-db/v1/api/credential';
import { FileCollectionSchema, FolderCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/file_system';
import { FlowCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/flow';
-import { GraphQLCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/graph_q_l';
+import { GraphQLCollectionSchema, GraphQLDeltaCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/graph_q_l';
import { HttpCollectionSchema, HttpDeltaCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/http';
+import { WebSocketCollectionSchema } from '@the-dev-tools/spec/tanstack-db/v1/api/web_socket';
import { Button } from '@the-dev-tools/ui/button';
import { FlowsIcon, FolderOpenedIcon } from '@the-dev-tools/ui/icons';
import { Menu, MenuItem, useContextMenuState } from '@the-dev-tools/ui/menu';
@@ -89,6 +94,7 @@ export const FileCreateMenu = ({ parentFolderId, ...props }: FileCreateMenuProps
const graphqlCollection = useApiCollection(GraphQLCollectionSchema);
const httpCollection = useApiCollection(HttpCollectionSchema);
const flowCollection = useApiCollection(FlowCollectionSchema);
+ const websocketCollection = useApiCollection(WebSocketCollectionSchema);
const insertFile = useInsertFile(parentFolderId);
@@ -152,6 +158,16 @@ export const FileCreateMenu = ({ parentFolderId, ...props }: FileCreateMenuProps
Flow
+
+
);
@@ -277,7 +293,9 @@ export const FileTree = ({ onAction, onSelectionChange, selectedKeys, selectionM
if (dropPosition !== 'on') return false;
const sourceCanMove =
- !sourceKinds.has(`kind_${FileKind.UNSPECIFIED}`) && !sourceKinds.has(`kind_${FileKind.HTTP_DELTA}`);
+ !sourceKinds.has(`kind_${FileKind.UNSPECIFIED}`) &&
+ !sourceKinds.has(`kind_${FileKind.HTTP_DELTA}`) &&
+ !sourceKinds.has(`kind_${FileKind.GRAPH_Q_L_DELTA}`);
const targetCanAccept = fileCollection.get(key.toString())?.kind === FileKind.FOLDER;
return sourceCanMove && targetCanAccept;
@@ -352,7 +370,9 @@ const FileItem = ({ id }: FileItemProps) => {
Match.when(FileKind.HTTP_DELTA, () => ),
Match.when(FileKind.FLOW, () => ),
Match.when(FileKind.GRAPH_Q_L, () => ),
+ Match.when(FileKind.GRAPH_Q_L_DELTA, () => ),
Match.when(FileKind.CREDENTIAL, () => ),
+ Match.when(FileKind.WEB_SOCKET, () => ),
Match.orElse(() => null),
);
};
@@ -905,8 +925,13 @@ const FlowFile = ({ id }: FileItemProps) => {
};
const GraphQLFile = ({ id }: FileItemProps) => {
- const router = useRouter();
const matchRoute = useMatchRoute();
+ const router = useRouter();
+ const navigate = useNavigate();
+
+ const { theme } = useTheme();
+
+ const { workspaceId } = routes.dashboard.workspace.route.useLoaderData();
const fileCollection = useApiCollection(FileCollectionSchema);
@@ -924,6 +949,22 @@ const GraphQLFile = ({ id }: FileItemProps) => {
[graphqlCollection, graphqlId],
).data ?? create(GraphQLItemSchema);
+ const deltaCollection = useApiCollection(GraphQLDeltaCollectionSchema);
+
+ const { data: files } = useLiveQuery(
+ (_) =>
+ _.from({ file: fileCollection })
+ .where((_) => eq(_.file.parentId, graphqlId))
+ .orderBy((_) => _.file.order)
+ .select((_) => pick(_.file, 'fileId', 'order')),
+ [fileCollection, graphqlId],
+ );
+
+ const modal = useProgrammaticModal();
+
+ const exportMutation = useConnectMutation(ExportService.method.export);
+ const exportCurlGraphQLMutation = useConnectMutation(ExportService.method.exportCurlGraphQL);
+
const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext);
const { escapeRef, escapeRender } = useEscapePortal(containerRef);
@@ -943,6 +984,8 @@ const GraphQLFile = ({ id }: FileItemProps) => {
const content = (
<>
+ {modal.children && }
+
GQL
@@ -966,8 +1009,361 @@ const GraphQLFile = ({ id }: FileItemProps) => {
+
+ )}
+ >
+ );
+
+ const props = {
+ children: content,
+ className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '',
+ id,
+ item: (_) => ,
+ items: files,
+ onContextMenu,
+ textValue: name,
+ } satisfies TreeItemProps<(typeof files)[number]>;
+
+ return toNavigate ? : ;
+};
+
+const GraphQLDeltaFile = ({ id }: FileItemProps) => {
+ const router = useRouter();
+ const matchRoute = useMatchRoute();
+
+ const { theme } = useTheme();
+
+ const { workspaceId } = routes.dashboard.workspace.route.useLoaderData();
+
+ const fileCollection = useApiCollection(FileCollectionSchema);
+
+ const { fileId: deltaGraphqlId } = useMemo(
+ () => fileCollection.utils.parseKeyUnsafe(id),
+ [fileCollection.utils, id],
+ );
+
+ const deltaCollection = useApiCollection(GraphQLDeltaCollectionSchema);
+
+ const { graphqlId } =
+ useLiveQuery(
+ (_) =>
+ _.from({ item: deltaCollection })
+ .where((_) => eq(_.item.deltaGraphqlId, deltaGraphqlId))
+ .select((_) => pick(_.item, 'graphqlId'))
+ .findOne(),
+ [deltaCollection, deltaGraphqlId],
+ ).data ?? create(GraphQLDeltaSchema);
+
+ const deltaOptions = {
+ deltaId: deltaGraphqlId,
+ deltaSchema: GraphQLDeltaCollectionSchema,
+ originId: graphqlId,
+ originSchema: GraphQLCollectionSchema,
+ } as const;
+
+ const [name, setName] = useDeltaState({ ...deltaOptions, valueKey: 'name' });
+
+ const modal = useProgrammaticModal();
+
+ const exportMutation = useConnectMutation(ExportService.method.export);
+ const exportCurlGraphQLMutation = useConnectMutation(ExportService.method.exportCurlGraphQL);
+
+ const { containerRef, navigate: toNavigate = false, showControls } = useContext(FileTreeContext);
+
+ const { escapeRef, escapeRender } = useEscapePortal(containerRef);
+
+ const { edit, isEditing, textFieldProps } = useEditableTextState({
+ onSuccess: (_) => {
+ if (_ === name) return;
+ setName(_);
+ },
+ value: name ?? '',
+ });
+
+ const { menuProps, menuTriggerProps, onContextMenu } = useContextMenuState();
+
+ const route = {
+ from: router.routesById[routes.dashboard.workspace.route.id].fullPath,
+ params: {
+ deltaGraphqlIdCan: Ulid.construct(deltaGraphqlId).toCanonical(),
+ graphqlIdCan: Ulid.construct(graphqlId).toCanonical(),
+ },
+ to: router.routesById[routes.dashboard.workspace.graphql.delta.id].fullPath,
+ } satisfies ToOptions;
+
+ const content = (
+ <>
+ {modal.children && }
+
+ GQL
+
+
+ {name}
+
+
+ {isEditing &&
+ escapeRender(
+ ,
+ )}
+
+ {showControls && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+
+ const props = {
+ children: content,
+ className: toNavigate && matchRoute(route) !== false ? tw`bg-neutral` : '',
+ id,
+ onContextMenu,
+ textValue: name ?? '',
+ } satisfies TreeItemProps