@automattic/vip 4.1.4 → 4.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/vip-next/commands/devenv.go +1 -0
- package/cmd/vip-next/commands/devenv_envvar.go +6 -0
- package/cmd/vip-next/commands/devenv_lifecycle.go +7 -0
- package/cmd/vip-next/commands/devenv_resolve.go +15 -3
- package/cmd/vip-next/commands/devenv_telemetry.go +265 -0
- package/cmd/vip-next/commands/devenv_telemetry_test.go +322 -0
- package/dist/lib/dev-environment/dev-environment-cli.js +8 -5
- package/node_modules/@apollo/client/CHANGELOG.md +6 -0
- package/node_modules/@apollo/client/__cjs/react/hooks/useLazyQuery.cjs +27 -1
- package/node_modules/@apollo/client/__cjs/react/hooks/useLazyQuery.cjs.map +1 -1
- package/node_modules/@apollo/client/__cjs/version.cjs +1 -1
- package/node_modules/@apollo/client/package.json +1 -1
- package/node_modules/@apollo/client/react/hooks/useLazyQuery.js +27 -1
- package/node_modules/@apollo/client/react/hooks/useLazyQuery.js.map +1 -1
- package/node_modules/@apollo/client/react/hooks-compiled/useLazyQuery.js +27 -1
- package/node_modules/@apollo/client/react/hooks-compiled/useLazyQuery.js.map +1 -1
- package/node_modules/@apollo/client/version.js +1 -1
- package/node_modules/@types/node/README.md +2 -2
- package/node_modules/@types/node/child_process.d.ts +1 -1
- package/node_modules/@types/node/crypto.d.ts +20 -8
- package/node_modules/@types/node/ffi.d.ts +14 -0
- package/node_modules/@types/node/http2.d.ts +3 -2
- package/node_modules/@types/node/net.d.ts +16 -4
- package/node_modules/@types/node/package.json +2 -17
- package/node_modules/@types/node/process.d.ts +1 -0
- package/node_modules/@types/node/quic.d.ts +39 -1
- package/node_modules/@types/node/stream/iter.d.ts +9 -12
- package/node_modules/@types/node/test/reporters.d.ts +1 -0
- package/node_modules/@types/node/test.d.ts +72 -0
- package/node_modules/@types/node/tls.d.ts +14 -10
- package/node_modules/@types/node/vm.d.ts +2 -1
- package/node_modules/@types/node/worker_threads.d.ts +4 -1
- package/node_modules/socket.io-client/README.md +1 -0
- package/node_modules/socket.io-client/build/cjs/index.d.ts +3 -3
- package/node_modules/socket.io-client/build/cjs/manager.d.ts +1 -2
- package/node_modules/socket.io-client/build/cjs/manager.js +17 -7
- package/node_modules/socket.io-client/build/cjs/socket.d.ts +5 -2
- package/node_modules/socket.io-client/build/cjs/socket.js +0 -1
- package/node_modules/socket.io-client/build/esm/index.d.ts +3 -3
- package/node_modules/socket.io-client/build/esm/index.js +1 -1
- package/node_modules/socket.io-client/build/esm/manager.d.ts +1 -2
- package/node_modules/socket.io-client/build/esm/package.json +1 -1
- package/node_modules/socket.io-client/build/esm/socket.d.ts +5 -2
- package/node_modules/socket.io-client/build/esm/socket.js +0 -1
- package/node_modules/socket.io-client/build/esm-debug/index.d.ts +3 -3
- package/node_modules/socket.io-client/build/esm-debug/index.js +1 -1
- package/node_modules/socket.io-client/build/esm-debug/manager.d.ts +1 -2
- package/node_modules/socket.io-client/build/esm-debug/package.json +1 -1
- package/node_modules/socket.io-client/build/esm-debug/socket.d.ts +5 -2
- package/node_modules/socket.io-client/build/esm-debug/socket.js +0 -1
- package/node_modules/socket.io-client/dist/socket.io.esm.min.js +3 -3
- package/node_modules/socket.io-client/dist/socket.io.esm.min.js.map +1 -1
- package/node_modules/socket.io-client/dist/socket.io.js +174 -242
- package/node_modules/socket.io-client/dist/socket.io.js.map +1 -1
- package/node_modules/socket.io-client/dist/socket.io.min.js +3 -3
- package/node_modules/socket.io-client/dist/socket.io.min.js.map +1 -1
- package/node_modules/socket.io-client/dist/socket.io.msgpack.min.js +3 -3
- package/node_modules/socket.io-client/dist/socket.io.msgpack.min.js.map +1 -1
- package/node_modules/socket.io-client/package.json +2 -2
- package/node_modules/undici/README.md +10 -1
- package/node_modules/undici/docs/docs/api/Agent.md +8 -8
- package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +9 -6
- package/node_modules/undici/docs/docs/api/Dispatcher.md +9 -0
- package/node_modules/undici/docs/docs/api/Errors.md +25 -0
- package/node_modules/undici/docs/docs/api/Fetch.md +10 -0
- package/node_modules/undici/docs/docs/api/Interceptors.md +4 -3
- package/node_modules/undici/docs/docs/api/Pool.md +5 -6
- package/node_modules/undici/docs/docs/index.md +10 -1
- package/node_modules/undici/lib/api/readable.js +1 -2
- package/node_modules/undici/lib/core/request.js +23 -2
- package/node_modules/undici/lib/core/util.js +0 -39
- package/node_modules/undici/lib/dispatcher/client-h1.js +11 -4
- package/node_modules/undici/lib/dispatcher/client-h2.js +52 -8
- package/node_modules/undici/lib/dispatcher/client.js +6 -1
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +21 -6
- package/node_modules/undici/lib/dispatcher/pool-base.js +63 -2
- package/node_modules/undici/lib/dispatcher/pool.js +90 -7
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +16 -3
- package/node_modules/undici/lib/handler/deduplication-handler.js +24 -11
- package/node_modules/undici/lib/handler/redirect-handler.js +5 -0
- package/node_modules/undici/lib/handler/retry-handler.js +72 -13
- package/node_modules/undici/lib/interceptor/cache.js +4 -1
- package/node_modules/undici/lib/interceptor/decompress.js +236 -69
- package/node_modules/undici/lib/web/cache/cache.js +17 -8
- package/node_modules/undici/lib/web/cache/cachestorage.js +11 -5
- package/node_modules/undici/lib/web/cookies/index.js +19 -7
- package/node_modules/undici/lib/web/cookies/util.js +3 -1
- package/node_modules/undici/lib/web/eventsource/eventsource.js +25 -1
- package/node_modules/undici/lib/web/fetch/body.js +22 -15
- package/node_modules/undici/lib/web/fetch/data-url.js +6 -6
- package/node_modules/undici/lib/web/fetch/formdata.js +30 -36
- package/node_modules/undici/lib/web/fetch/headers.js +34 -31
- package/node_modules/undici/lib/web/fetch/index.js +46 -25
- package/node_modules/undici/lib/web/fetch/request.js +74 -79
- package/node_modules/undici/lib/web/fetch/response.js +43 -44
- package/node_modules/undici/lib/web/fetch/util.js +7 -7
- package/node_modules/undici/lib/web/infra/index.js +16 -1
- package/node_modules/undici/lib/web/webidl/index.js +2 -14
- package/node_modules/undici/lib/web/websocket/connection.js +33 -12
- package/node_modules/undici/lib/web/websocket/events.js +43 -26
- package/node_modules/undici/lib/web/websocket/receiver.js +2 -0
- package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +13 -10
- package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +19 -4
- package/node_modules/undici/lib/web/websocket/websocket.js +44 -41
- package/node_modules/undici/package.json +1 -1
- package/node_modules/undici/types/diagnostics-channel.d.ts +2 -1
- package/node_modules/undici/types/interceptors.d.ts +1 -1
- package/node_modules/undici/types/webidl.d.ts +13 -7
- package/package.json +2 -2
|
@@ -116,9 +116,13 @@ func envvarSetCmd() *cobra.Command {
|
|
|
116
116
|
// a valid shell identifier and the container silently ignores it.
|
|
117
117
|
name := strings.TrimSpace(args[0])
|
|
118
118
|
if name == "" {
|
|
119
|
+
devEnvTrackingSpecial(cmd, "dev_env_envvar_set_invalid_name")
|
|
120
|
+
devEnvTrackingCancelled(cmd)
|
|
119
121
|
return errors.New("variable name is required")
|
|
120
122
|
}
|
|
121
123
|
if err := envvar.ValidateName(name); err != nil {
|
|
124
|
+
devEnvTrackingSpecial(cmd, "dev_env_envvar_set_invalid_name")
|
|
125
|
+
devEnvTrackingCancelled(cmd)
|
|
122
126
|
return err
|
|
123
127
|
}
|
|
124
128
|
debuglog.Printf(cmd.Context(), "@automattic/vip:bin:config:envvar", "Will set variable %q for environment %q: fromFile=%t", name, slug, fromFile != "")
|
|
@@ -138,6 +142,8 @@ func envvarSetCmd() *cobra.Command {
|
|
|
138
142
|
default:
|
|
139
143
|
value, err = appctx.Input(cmd, fmt.Sprintf("Value for %s", name), "")
|
|
140
144
|
if err != nil {
|
|
145
|
+
devEnvTrackingSpecial(cmd, "dev_env_envvar_set_user_cancelled_input")
|
|
146
|
+
devEnvTrackingCancelled(cmd)
|
|
141
147
|
return err
|
|
142
148
|
}
|
|
143
149
|
}
|
|
@@ -324,6 +324,7 @@ func runDevEnvCreate(cmd *cobra.Command, _ []string) error {
|
|
|
324
324
|
if err != nil {
|
|
325
325
|
return err
|
|
326
326
|
}
|
|
327
|
+
devEnvTrackingCreate(cmd, cfg.Slug)
|
|
327
328
|
ctx, finish := openDevEnvLog(cmd, cfg.Slug, true)
|
|
328
329
|
defer finish()
|
|
329
330
|
if err := devenv.Create(ctx, cfg); err != nil {
|
|
@@ -612,6 +613,7 @@ func devEnvStartCmd() *cobra.Command {
|
|
|
612
613
|
msg := fmt.Sprintf("Found an existing Lando environment %q. vip-next will take it over — reusing its database and removing the old Lando containers (your data volume is kept). This process is irreversible. Continue?", slug)
|
|
613
614
|
confirmed, cerr := appctx.Confirm(cmd, msg, false)
|
|
614
615
|
if cerr == appctx.ErrNonInteractive || (!confirmed && cerr == nil) {
|
|
616
|
+
devEnvTrackingCancelled(cmd)
|
|
615
617
|
fmt.Fprintln(cmd.OutOrStdout(), "Command cancelled")
|
|
616
618
|
return nil
|
|
617
619
|
}
|
|
@@ -690,6 +692,7 @@ func devEnvDestroyCmd() *cobra.Command {
|
|
|
690
692
|
return err
|
|
691
693
|
}
|
|
692
694
|
if !ok {
|
|
695
|
+
devEnvTrackingCancelled(cmd)
|
|
693
696
|
return nil
|
|
694
697
|
}
|
|
695
698
|
}
|
|
@@ -766,9 +769,13 @@ func devEnvPurgeCmd() *cobra.Command {
|
|
|
766
769
|
return err
|
|
767
770
|
}
|
|
768
771
|
if !ok {
|
|
772
|
+
devEnvTrackingCancelled(cmd)
|
|
769
773
|
return nil
|
|
770
774
|
}
|
|
771
775
|
}
|
|
776
|
+
if len(instancedata.AllNames()) > 0 {
|
|
777
|
+
devEnvTrackingBegin(cmd, map[string]any{"all": true})
|
|
778
|
+
}
|
|
772
779
|
return devenv.Purge(cmd.Context(), soft)
|
|
773
780
|
}}
|
|
774
781
|
c.Flags().BoolVar(&yes, "yes", false, "Skip the confirmation prompt.")
|
|
@@ -31,7 +31,9 @@ import (
|
|
|
31
31
|
// environment found" error / DEFAULT_SLUG fallback.
|
|
32
32
|
func ResolveSlug(cmd *cobra.Command) (string, error) {
|
|
33
33
|
if s, _ := cmd.Flags().GetString("slug"); s != "" {
|
|
34
|
-
|
|
34
|
+
slug := nodeflags.ProcessSlug(s)
|
|
35
|
+
devEnvTrackingResolved(cmd, slug)
|
|
36
|
+
return slug, nil
|
|
35
37
|
}
|
|
36
38
|
if err := rejectAppEnvAlias(cmd); err != nil {
|
|
37
39
|
return "", err
|
|
@@ -46,10 +48,15 @@ func ResolveSlug(cmd *cobra.Command) (string, error) {
|
|
|
46
48
|
// options before calling getEnvironmentName (vip-dev-env-sync-sql.js:98).
|
|
47
49
|
func ResolveLocalSlug(cmd *cobra.Command) (string, error) {
|
|
48
50
|
if s, _ := cmd.Flags().GetString("slug"); s != "" {
|
|
49
|
-
|
|
51
|
+
slug := nodeflags.ProcessSlug(s)
|
|
52
|
+
devEnvTrackingResolved(cmd, slug)
|
|
53
|
+
return slug, nil
|
|
50
54
|
}
|
|
51
55
|
slug, err := configFileSlug(cmd)
|
|
52
56
|
if err != nil || slug != "" {
|
|
57
|
+
if err == nil {
|
|
58
|
+
devEnvTrackingResolved(cmd, slug)
|
|
59
|
+
}
|
|
53
60
|
return slug, err
|
|
54
61
|
}
|
|
55
62
|
names := instancedata.AllNames()
|
|
@@ -57,10 +64,15 @@ func ResolveLocalSlug(cmd *cobra.Command) (string, error) {
|
|
|
57
64
|
case 0:
|
|
58
65
|
return "", errors.New("no dev environments found; create one with `vip dev-env create`")
|
|
59
66
|
case 1:
|
|
67
|
+
devEnvTrackingResolved(cmd, names[0])
|
|
60
68
|
return names[0], nil
|
|
61
69
|
}
|
|
62
70
|
if appctx.IsInteractive(cmd) {
|
|
63
|
-
|
|
71
|
+
slug, err := appctx.Select(cmd, "Which environment?", names)
|
|
72
|
+
if err == nil {
|
|
73
|
+
devEnvTrackingResolved(cmd, slug)
|
|
74
|
+
}
|
|
75
|
+
return slug, err
|
|
64
76
|
}
|
|
65
77
|
return "", fmt.Errorf("multiple environments found; specify --slug: %w", appctx.ErrNonInteractive)
|
|
66
78
|
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
package commands
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"errors"
|
|
8
|
+
"math"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strings"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/Automattic/vip/internal/appctx"
|
|
15
|
+
"github.com/Automattic/vip/internal/devenv"
|
|
16
|
+
"github.com/Automattic/vip/internal/devenv/dockercli"
|
|
17
|
+
"github.com/Automattic/vip/internal/devenv/paths"
|
|
18
|
+
"github.com/Automattic/vip/internal/searchreplace"
|
|
19
|
+
"github.com/Automattic/vip/internal/telemetry"
|
|
20
|
+
"github.com/spf13/cobra"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type devEnvTelemetryKey struct{}
|
|
24
|
+
|
|
25
|
+
type devEnvTelemetryState struct {
|
|
26
|
+
name string
|
|
27
|
+
args []string
|
|
28
|
+
info map[string]any
|
|
29
|
+
started time.Time
|
|
30
|
+
canceled bool
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var devEnvEventNames = map[string]string{
|
|
34
|
+
"create": "dev_env_create_command_", "start": "dev_env_start_command_",
|
|
35
|
+
"stop": "dev_env_stop_command_", "destroy": "dev_env_destroy_command_",
|
|
36
|
+
"info": "dev_env_info_command_", "list": "dev_env_list_command_",
|
|
37
|
+
"purge": "dev_env_purge_command_", "update": "dev_env_update_command_",
|
|
38
|
+
"exec": "dev_env_exec_command_", "shell": "dev_env_shell_command_",
|
|
39
|
+
"logs": "dev_env_logs_command_", "import sql": "dev_env_import_sql_command_",
|
|
40
|
+
"import media": "dev_env_import_media_command_", "sync sql": "dev_env_sync_sql_command_",
|
|
41
|
+
"envvar get": "dev_env_envvar_get_command_", "envvar get-all": "dev_env_envvar_get_all_command_",
|
|
42
|
+
"envvar list": "dev_env_envvar_list_command_", "envvar set": "dev_env_envvar_set_command_",
|
|
43
|
+
"envvar delete": "dev_env_envvar_delete_command_",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// installDevEnvTelemetry wraps leaves after their middleware is built. Slug
|
|
47
|
+
// commands emit execute from ResolveSlug, after resolution as Node does.
|
|
48
|
+
func installDevEnvTelemetry(root *cobra.Command) {
|
|
49
|
+
var visit func(*cobra.Command, string)
|
|
50
|
+
visit = func(parent *cobra.Command, prefix string) {
|
|
51
|
+
for _, child := range parent.Commands() {
|
|
52
|
+
path := strings.TrimSpace(prefix + " " + child.Name())
|
|
53
|
+
if child.RunE != nil {
|
|
54
|
+
if name, ok := devEnvEventNames[path]; ok {
|
|
55
|
+
original := child.RunE
|
|
56
|
+
child.RunE = func(cmd *cobra.Command, args []string) error {
|
|
57
|
+
state := &devEnvTelemetryState{name: name, args: args}
|
|
58
|
+
ctx := cmd.Context()
|
|
59
|
+
if ctx == nil {
|
|
60
|
+
ctx = context.Background()
|
|
61
|
+
}
|
|
62
|
+
cmd.SetContext(context.WithValue(ctx, devEnvTelemetryKey{}, state))
|
|
63
|
+
if path == "list" || ((path == "info" || path == "stop") && devEnvAllFlag(cmd)) {
|
|
64
|
+
devEnvTrackingBegin(cmd, map[string]any{"all": true})
|
|
65
|
+
}
|
|
66
|
+
err := original(cmd, args)
|
|
67
|
+
if state.started.IsZero() || state.canceled {
|
|
68
|
+
return err
|
|
69
|
+
}
|
|
70
|
+
if err != nil {
|
|
71
|
+
if path == "sync sql" && errors.Is(err, devenv.ErrEnvironmentNotStarted) {
|
|
72
|
+
props := devEnvTrackingCopy(state.info)
|
|
73
|
+
props["errorMessage"] = "Environment was not running"
|
|
74
|
+
trackEvent(name+"env_not_running_error", props)
|
|
75
|
+
return err
|
|
76
|
+
}
|
|
77
|
+
props := devEnvTrackingCopy(state.info)
|
|
78
|
+
props["failure"] = telemetry.ScrubErrorText(err.Error())
|
|
79
|
+
trackEvent(name+"error", props)
|
|
80
|
+
return err
|
|
81
|
+
}
|
|
82
|
+
props := devEnvTrackingCopy(state.info)
|
|
83
|
+
if path == "start" {
|
|
84
|
+
props["processing_time"] = int64(math.Ceil(time.Since(state.started).Seconds()))
|
|
85
|
+
}
|
|
86
|
+
trackEvent(name+"success", props)
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
visit(child, path)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
visit(root, "")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func devEnvAllFlag(cmd *cobra.Command) bool {
|
|
98
|
+
if cmd.Flags().Lookup("all") == nil {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
all, _ := cmd.Flags().GetBool("all")
|
|
102
|
+
return all
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
func devEnvTrackingCopy(info map[string]any) map[string]any {
|
|
106
|
+
copyInfo := make(map[string]any, len(info))
|
|
107
|
+
for k, v := range info {
|
|
108
|
+
copyInfo[k] = v
|
|
109
|
+
}
|
|
110
|
+
return copyInfo
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func devEnvTrackingState(cmd *cobra.Command) *devEnvTelemetryState {
|
|
114
|
+
ctx := cmd.Context()
|
|
115
|
+
if ctx == nil {
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
state, _ := ctx.Value(devEnvTelemetryKey{}).(*devEnvTelemetryState)
|
|
119
|
+
return state
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func devEnvTrackingBegin(cmd *cobra.Command, info map[string]any) {
|
|
123
|
+
state := devEnvTrackingState(cmd)
|
|
124
|
+
if state == nil || !state.started.IsZero() {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
state.info = devEnvTrackingCopy(info)
|
|
128
|
+
state.started = time.Now()
|
|
129
|
+
trackEvent(state.name+"execute", devEnvTrackingCopy(state.info))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
func devEnvTrackingResolved(cmd *cobra.Command, slug string) {
|
|
133
|
+
state := devEnvTrackingState(cmd)
|
|
134
|
+
if state == nil {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
info := devEnvTrackingInfo(slug)
|
|
138
|
+
if cmd.Name() == "sql" && cmd.Parent() != nil && cmd.Parent().Name() == "import" {
|
|
139
|
+
if len(state.args) > 0 {
|
|
140
|
+
details, err := searchreplace.GetSqlDumpDetails(state.args[0])
|
|
141
|
+
if err != nil {
|
|
142
|
+
return // Node determines dump type before its execute event.
|
|
143
|
+
}
|
|
144
|
+
info["sqldump_type"] = string(details.Type)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if cmd.Name() == "sql" && cmd.Parent() != nil && cmd.Parent().Name() == "sync" {
|
|
148
|
+
if ae := appctx.FromContext(cmd.Context()); ae != nil {
|
|
149
|
+
info = map[string]any{
|
|
150
|
+
"app": ae.App.ID, "env": ae.Env.UniqueLabel,
|
|
151
|
+
"slug": slug, "multisite": ae.Env.IsMultisite,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if cmd.Name() == "start" {
|
|
156
|
+
editor, _ := cmd.Flags().GetString("editor")
|
|
157
|
+
vscode, _ := cmd.Flags().GetBool("vscode")
|
|
158
|
+
if editor == "" && vscode {
|
|
159
|
+
editor = "vscode"
|
|
160
|
+
}
|
|
161
|
+
if editor != "" {
|
|
162
|
+
info["editor"] = editor
|
|
163
|
+
}
|
|
164
|
+
info["vscode"] = vscode
|
|
165
|
+
versions := (&dockercli.Runner{}).Versions(cmd.Context())
|
|
166
|
+
info["docker"] = versions.Engine
|
|
167
|
+
info["docker_compose"] = versions.Compose
|
|
168
|
+
info["compose_plugin"] = versions.ComposePlugin
|
|
169
|
+
}
|
|
170
|
+
devEnvTrackingBegin(cmd, info)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func devEnvTrackingCreate(cmd *cobra.Command, slug string) {
|
|
174
|
+
info := map[string]any{"slug": slug}
|
|
175
|
+
if app, _ := cmd.Flags().GetString("app"); app != "" {
|
|
176
|
+
info["app"] = app
|
|
177
|
+
}
|
|
178
|
+
if env, _ := cmd.Flags().GetString("env"); env != "" {
|
|
179
|
+
info["env"] = env
|
|
180
|
+
}
|
|
181
|
+
devEnvTrackingBegin(cmd, info)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func devEnvTrackingCancelled(cmd *cobra.Command) {
|
|
185
|
+
state := devEnvTrackingState(cmd)
|
|
186
|
+
if state != nil {
|
|
187
|
+
state.canceled = true
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func devEnvTrackingSpecial(cmd *cobra.Command, name string) {
|
|
192
|
+
state := devEnvTrackingState(cmd)
|
|
193
|
+
if state != nil && !state.started.IsZero() {
|
|
194
|
+
trackEvent(name, devEnvTrackingCopy(state.info))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// devEnvTrackingInfo mirrors Node's getEnvTrackingInfo for the shared instance
|
|
199
|
+
// fields. Only known Node fields are eligible: Go also stores user environment
|
|
200
|
+
// variables and volume metadata in instance_data.json, neither of which belongs
|
|
201
|
+
// in telemetry. Local login credentials are excluded in both runtimes.
|
|
202
|
+
func devEnvTrackingInfo(slug string) map[string]any {
|
|
203
|
+
info := map[string]any{"slug": slug}
|
|
204
|
+
data, err := os.ReadFile(filepath.Join(paths.EnvironmentPath(slug), "instance_data.json"))
|
|
205
|
+
if err != nil {
|
|
206
|
+
return info
|
|
207
|
+
}
|
|
208
|
+
var fields map[string]json.RawMessage
|
|
209
|
+
if err := json.Unmarshal(data, &fields); err != nil {
|
|
210
|
+
return info
|
|
211
|
+
}
|
|
212
|
+
// Node's readEnvironmentData maps these legacy names before tracking.
|
|
213
|
+
if legacy, ok := fields["clientCode"]; ok && string(legacy) != "null" {
|
|
214
|
+
fields["appCode"] = legacy
|
|
215
|
+
}
|
|
216
|
+
for _, legacyKey := range []string{"enterpriseSearchEnabled", "elasticsearchEnabled"} {
|
|
217
|
+
if legacy, ok := fields[legacyKey]; ok && string(legacy) == "true" {
|
|
218
|
+
fields["elasticsearch"] = legacy
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for _, key := range []string{
|
|
223
|
+
"siteSlug", "wpTitle", "multisite", "wordpress", "muPlugins", "appCode",
|
|
224
|
+
"mediaRedirectDomain", "phpmyadmin", "xdebug", "xdebugConfig", "mariadb",
|
|
225
|
+
"php", "elasticsearch", "mailpit", "photon", "cron", "pullAfter",
|
|
226
|
+
"version",
|
|
227
|
+
} {
|
|
228
|
+
raw, ok := fields[key]
|
|
229
|
+
if !ok {
|
|
230
|
+
continue
|
|
231
|
+
}
|
|
232
|
+
var value any
|
|
233
|
+
if key == "wordpress" || key == "muPlugins" || key == "appCode" {
|
|
234
|
+
var compact bytes.Buffer
|
|
235
|
+
if err := json.Compact(&compact, raw); err != nil {
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
value = compact.String()
|
|
239
|
+
} else if err := json.Unmarshal(raw, &value); err != nil {
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
if key == "php" {
|
|
243
|
+
if php, ok := value.(string); ok {
|
|
244
|
+
if colon := strings.IndexByte(php, ':'); colon >= 0 {
|
|
245
|
+
value = php[colon+1:]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
info[devEnvSnakeCase(key)] = value
|
|
250
|
+
}
|
|
251
|
+
return info
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
func devEnvSnakeCase(key string) string {
|
|
255
|
+
var b strings.Builder
|
|
256
|
+
for _, r := range key {
|
|
257
|
+
if r >= 'A' && r <= 'Z' {
|
|
258
|
+
b.WriteByte('_')
|
|
259
|
+
b.WriteRune(r + ('a' - 'A'))
|
|
260
|
+
} else {
|
|
261
|
+
b.WriteRune(r)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return b.String()
|
|
265
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
package commands
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"errors"
|
|
5
|
+
"io"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"github.com/Automattic/vip/internal/devenv"
|
|
12
|
+
"github.com/Automattic/vip/internal/devenv/paths"
|
|
13
|
+
"github.com/Automattic/vip/internal/telemetry"
|
|
14
|
+
"github.com/spf13/cobra"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type devEnvCapturedEvent struct {
|
|
18
|
+
name string
|
|
19
|
+
props map[string]any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type devEnvEventClient struct{ events []devEnvCapturedEvent }
|
|
23
|
+
|
|
24
|
+
func (c *devEnvEventClient) TrackEvent(name string, props map[string]any) error {
|
|
25
|
+
copyProps := make(map[string]any, len(props))
|
|
26
|
+
for k, v := range props {
|
|
27
|
+
copyProps[k] = v
|
|
28
|
+
}
|
|
29
|
+
c.events = append(c.events, devEnvCapturedEvent{name: name, props: copyProps})
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestDevEnvTrackingInfoOmitsCredentials(t *testing.T) {
|
|
34
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
35
|
+
slug := "example-site"
|
|
36
|
+
if err := os.MkdirAll(paths.EnvironmentPath(slug), 0o755); err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
data := []byte(`{"siteSlug":"example-site","wpTitle":"Example Site","php":"php-fpm:8.4","adminPassword":"local-password","autologinKey":"local-key","envVars":{"API_TOKEN":"private-value"},"overrides":"API_TOKEN: private-value"}`)
|
|
40
|
+
if err := os.WriteFile(filepath.Join(paths.EnvironmentPath(slug), "instance_data.json"), data, 0o600); err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
got := devEnvTrackingInfo(slug)
|
|
45
|
+
if got["slug"] != slug || got["site_slug"] != slug || got["wp_title"] != "Example Site" || got["php"] != "8.4" {
|
|
46
|
+
t.Fatalf("safe tracking properties = %#v", got)
|
|
47
|
+
}
|
|
48
|
+
for _, key := range []string{"admin_password", "autologin_key", "env_vars", "overrides"} {
|
|
49
|
+
if _, ok := got[key]; ok {
|
|
50
|
+
t.Errorf("credential-bearing property %q was included", key)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func TestDevEnvTrackingInfoMissingRecord(t *testing.T) {
|
|
56
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
57
|
+
got := devEnvTrackingInfo("missing")
|
|
58
|
+
if len(got) != 1 || got["slug"] != "missing" {
|
|
59
|
+
t.Fatalf("missing-record tracking properties = %#v, want slug only", got)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func TestDevEnvTrackingInfoMapsLegacyFields(t *testing.T) {
|
|
64
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
65
|
+
slug := "legacy-site"
|
|
66
|
+
if err := os.MkdirAll(paths.EnvironmentPath(slug), 0o755); err != nil {
|
|
67
|
+
t.Fatal(err)
|
|
68
|
+
}
|
|
69
|
+
data := []byte(`{"siteSlug":"legacy-site","clientCode":{"repo":"example"},"elasticsearchEnabled":true}`)
|
|
70
|
+
if err := os.WriteFile(filepath.Join(paths.EnvironmentPath(slug), "instance_data.json"), data, 0o600); err != nil {
|
|
71
|
+
t.Fatal(err)
|
|
72
|
+
}
|
|
73
|
+
got := devEnvTrackingInfo(slug)
|
|
74
|
+
if got["app_code"] != `{"repo":"example"}` || got["elasticsearch"] != true {
|
|
75
|
+
t.Fatalf("legacy tracking properties = %#v", got)
|
|
76
|
+
}
|
|
77
|
+
if _, ok := got["client_code"]; ok {
|
|
78
|
+
t.Fatalf("legacy field was sent directly: %#v", got)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func TestDevEnvCommandTelemetryAfterSlugResolution(t *testing.T) {
|
|
83
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
84
|
+
t.Setenv("GO_ENV", "")
|
|
85
|
+
t.Setenv("NODE_ENV", "")
|
|
86
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
87
|
+
client := &devEnvEventClient{}
|
|
88
|
+
old := GetConfig()
|
|
89
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
90
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
91
|
+
|
|
92
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
93
|
+
info := &cobra.Command{Use: "info", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
94
|
+
_, err := ResolveSlug(cmd)
|
|
95
|
+
return err
|
|
96
|
+
}}
|
|
97
|
+
addSlugFlag(info)
|
|
98
|
+
root.AddCommand(info)
|
|
99
|
+
installDevEnvTelemetry(root)
|
|
100
|
+
root.SetArgs([]string{"info", "--slug=example-site"})
|
|
101
|
+
if err := root.Execute(); err != nil {
|
|
102
|
+
t.Fatal(err)
|
|
103
|
+
}
|
|
104
|
+
if len(client.events) != 2 || client.events[0].name != "dev_env_info_command_execute" || client.events[1].name != "dev_env_info_command_success" {
|
|
105
|
+
t.Fatalf("events = %#v", client.events)
|
|
106
|
+
}
|
|
107
|
+
if client.events[0].props["slug"] != "example-site" {
|
|
108
|
+
t.Fatalf("execute properties = %#v", client.events[0].props)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func TestDevEnvTelemetryDoesNotStartBeforeSlugResolution(t *testing.T) {
|
|
113
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
114
|
+
t.Setenv("GO_ENV", "")
|
|
115
|
+
t.Setenv("NODE_ENV", "")
|
|
116
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
117
|
+
t.Chdir(t.TempDir())
|
|
118
|
+
client := &devEnvEventClient{}
|
|
119
|
+
old := GetConfig()
|
|
120
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
121
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
122
|
+
|
|
123
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
124
|
+
info := &cobra.Command{Use: "info", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
125
|
+
_, err := ResolveSlug(cmd)
|
|
126
|
+
return err
|
|
127
|
+
}}
|
|
128
|
+
addSlugFlag(info)
|
|
129
|
+
root.AddCommand(info)
|
|
130
|
+
installDevEnvTelemetry(root)
|
|
131
|
+
root.SetArgs([]string{"info"})
|
|
132
|
+
if err := root.Execute(); err == nil {
|
|
133
|
+
t.Fatal("expected missing environment to fail")
|
|
134
|
+
}
|
|
135
|
+
if len(client.events) != 0 {
|
|
136
|
+
t.Fatalf("events before slug resolution = %#v", client.events)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func TestDevEnvTelemetryRecordsCommandError(t *testing.T) {
|
|
141
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
142
|
+
t.Setenv("GO_ENV", "")
|
|
143
|
+
t.Setenv("NODE_ENV", "")
|
|
144
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
145
|
+
client := &devEnvEventClient{}
|
|
146
|
+
old := GetConfig()
|
|
147
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
148
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
149
|
+
|
|
150
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
151
|
+
logs := &cobra.Command{Use: "logs", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
152
|
+
if _, err := ResolveSlug(cmd); err != nil {
|
|
153
|
+
return err
|
|
154
|
+
}
|
|
155
|
+
return errors.New("log stream failed")
|
|
156
|
+
}}
|
|
157
|
+
addSlugFlag(logs)
|
|
158
|
+
root.AddCommand(logs)
|
|
159
|
+
installDevEnvTelemetry(root)
|
|
160
|
+
root.SetArgs([]string{"logs", "--slug=example-site"})
|
|
161
|
+
if err := root.Execute(); err == nil {
|
|
162
|
+
t.Fatal("expected logs to fail")
|
|
163
|
+
}
|
|
164
|
+
if len(client.events) != 2 || client.events[1].name != "dev_env_logs_command_error" || client.events[1].props["failure"] != "log stream failed" {
|
|
165
|
+
t.Fatalf("error events = %#v", client.events)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func TestDevEnvCreateEmitsCommandEvents(t *testing.T) {
|
|
170
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
171
|
+
t.Setenv("GO_ENV", "")
|
|
172
|
+
t.Setenv("NODE_ENV", "")
|
|
173
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
174
|
+
t.Chdir(t.TempDir())
|
|
175
|
+
client := &devEnvEventClient{}
|
|
176
|
+
old := GetConfig()
|
|
177
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
178
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
179
|
+
|
|
180
|
+
root := DevEnvCmd()
|
|
181
|
+
root.SetOut(io.Discard)
|
|
182
|
+
root.SetErr(io.Discard)
|
|
183
|
+
root.SetArgs([]string{"create", "--slug=example-site", "--title=Example Site"})
|
|
184
|
+
if err := root.Execute(); err != nil {
|
|
185
|
+
t.Fatal(err)
|
|
186
|
+
}
|
|
187
|
+
if len(client.events) != 2 || client.events[0].name != "dev_env_create_command_execute" || client.events[1].name != "dev_env_create_command_success" {
|
|
188
|
+
t.Fatalf("create events = %#v", client.events)
|
|
189
|
+
}
|
|
190
|
+
if client.events[0].props["slug"] != "example-site" {
|
|
191
|
+
t.Fatalf("create properties = %#v", client.events[0].props)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func TestDevEnvImportSQLTracksDumpType(t *testing.T) {
|
|
196
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
197
|
+
t.Setenv("GO_ENV", "")
|
|
198
|
+
t.Setenv("NODE_ENV", "")
|
|
199
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
200
|
+
file := filepath.Join(t.TempDir(), "export.sql")
|
|
201
|
+
if err := os.WriteFile(file, []byte("-- ordinary SQL export\nCREATE TABLE wp_posts (id int);\n"), 0o600); err != nil {
|
|
202
|
+
t.Fatal(err)
|
|
203
|
+
}
|
|
204
|
+
client := &devEnvEventClient{}
|
|
205
|
+
old := GetConfig()
|
|
206
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
207
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
208
|
+
|
|
209
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
210
|
+
imp := &cobra.Command{Use: "import"}
|
|
211
|
+
sql := &cobra.Command{Use: "sql", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
212
|
+
_, err := ResolveSlug(cmd)
|
|
213
|
+
return err
|
|
214
|
+
}}
|
|
215
|
+
addSlugFlag(sql)
|
|
216
|
+
imp.AddCommand(sql)
|
|
217
|
+
root.AddCommand(imp)
|
|
218
|
+
installDevEnvTelemetry(root)
|
|
219
|
+
root.SetArgs([]string{"import", "sql", "--slug=example-site", file})
|
|
220
|
+
if err := root.Execute(); err != nil {
|
|
221
|
+
t.Fatal(err)
|
|
222
|
+
}
|
|
223
|
+
if len(client.events) != 2 || client.events[0].props["sqldump_type"] != "MYSQLDUMP" {
|
|
224
|
+
t.Fatalf("import SQL events = %#v", client.events)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func TestDevEnvEnvvarSetInvalidNameEvent(t *testing.T) {
|
|
229
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
230
|
+
t.Setenv("GO_ENV", "")
|
|
231
|
+
t.Setenv("NODE_ENV", "")
|
|
232
|
+
t.Setenv("XDG_DATA_HOME", t.TempDir())
|
|
233
|
+
client := &devEnvEventClient{}
|
|
234
|
+
old := GetConfig()
|
|
235
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
236
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
237
|
+
|
|
238
|
+
root := DevEnvCmd()
|
|
239
|
+
root.SetOut(io.Discard)
|
|
240
|
+
root.SetErr(io.Discard)
|
|
241
|
+
root.SetArgs([]string{"envvar", "set", "--slug=example-site", "invalid-name", "value"})
|
|
242
|
+
if err := root.Execute(); err == nil {
|
|
243
|
+
t.Fatal("expected invalid variable name to fail")
|
|
244
|
+
}
|
|
245
|
+
if len(client.events) != 2 || client.events[0].name != "dev_env_envvar_set_command_execute" || client.events[1].name != "dev_env_envvar_set_invalid_name" {
|
|
246
|
+
t.Fatalf("invalid-name events = %#v", client.events)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
func TestDevEnvEventNamesCoverEveryCommandLeaf(t *testing.T) {
|
|
251
|
+
root := DevEnvCmd()
|
|
252
|
+
seen := map[string]bool{}
|
|
253
|
+
var walk func(*cobra.Command, string)
|
|
254
|
+
walk = func(parent *cobra.Command, prefix string) {
|
|
255
|
+
for _, child := range parent.Commands() {
|
|
256
|
+
path := strings.TrimSpace(prefix + " " + child.Name())
|
|
257
|
+
if child.RunE != nil {
|
|
258
|
+
if _, ok := devEnvEventNames[path]; !ok {
|
|
259
|
+
t.Errorf("no event name for %q", path)
|
|
260
|
+
}
|
|
261
|
+
seen[path] = true
|
|
262
|
+
}
|
|
263
|
+
walk(child, path)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
walk(root, "")
|
|
267
|
+
for path := range devEnvEventNames {
|
|
268
|
+
if !seen[path] {
|
|
269
|
+
t.Errorf("event name %q has no command leaf", path)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func TestDevEnvAllCommandUsesAllProperty(t *testing.T) {
|
|
275
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
276
|
+
t.Setenv("GO_ENV", "")
|
|
277
|
+
t.Setenv("NODE_ENV", "")
|
|
278
|
+
client := &devEnvEventClient{}
|
|
279
|
+
old := GetConfig()
|
|
280
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
281
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
282
|
+
|
|
283
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
284
|
+
info := &cobra.Command{Use: "info", RunE: func(*cobra.Command, []string) error { return nil }}
|
|
285
|
+
info.Flags().Bool("all", false, "")
|
|
286
|
+
root.AddCommand(info)
|
|
287
|
+
installDevEnvTelemetry(root)
|
|
288
|
+
root.SetArgs([]string{"info", "--all"})
|
|
289
|
+
if err := root.Execute(); err != nil {
|
|
290
|
+
t.Fatal(err)
|
|
291
|
+
}
|
|
292
|
+
if len(client.events) != 2 || client.events[0].props["all"] != true || client.events[1].props["all"] != true {
|
|
293
|
+
t.Fatalf("all events = %#v", client.events)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func TestDevEnvSyncNotRunningUsesNodeEvent(t *testing.T) {
|
|
298
|
+
t.Setenv("DO_NOT_TRACK", "")
|
|
299
|
+
t.Setenv("GO_ENV", "")
|
|
300
|
+
t.Setenv("NODE_ENV", "")
|
|
301
|
+
client := &devEnvEventClient{}
|
|
302
|
+
old := GetConfig()
|
|
303
|
+
SetConfig(Config{Tracker: &telemetry.Tracker{Clients: []telemetry.Client{client}}})
|
|
304
|
+
t.Cleanup(func() { SetConfig(old) })
|
|
305
|
+
|
|
306
|
+
root := &cobra.Command{Use: "dev-env"}
|
|
307
|
+
sync := &cobra.Command{Use: "sync"}
|
|
308
|
+
sql := &cobra.Command{Use: "sql", RunE: func(cmd *cobra.Command, _ []string) error {
|
|
309
|
+
devEnvTrackingBegin(cmd, map[string]any{"slug": "example-site", "app": 123, "env": "develop", "multisite": false})
|
|
310
|
+
return devenv.ErrEnvironmentNotStarted
|
|
311
|
+
}}
|
|
312
|
+
sync.AddCommand(sql)
|
|
313
|
+
root.AddCommand(sync)
|
|
314
|
+
installDevEnvTelemetry(root)
|
|
315
|
+
root.SetArgs([]string{"sync", "sql"})
|
|
316
|
+
if err := root.Execute(); !errors.Is(err, devenv.ErrEnvironmentNotStarted) {
|
|
317
|
+
t.Fatalf("got error %v", err)
|
|
318
|
+
}
|
|
319
|
+
if len(client.events) != 2 || client.events[1].name != "dev_env_sync_sql_command_env_not_running_error" || client.events[1].props["errorMessage"] != "Environment was not running" {
|
|
320
|
+
t.Fatalf("sync events = %#v", client.events)
|
|
321
|
+
}
|
|
322
|
+
}
|