@vercel/go 3.4.1 → 3.4.2
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/dist/index.js +29 -4
- package/package.json +2 -2
- package/vc-init-dev.go +147 -0
- package/vc-init.go +10 -32
- package/vc-utils.go +114 -0
package/dist/index.js
CHANGED
|
@@ -17201,11 +17201,13 @@ async function buildStandaloneServer({
|
|
|
17201
17201
|
throw err;
|
|
17202
17202
|
}
|
|
17203
17203
|
const bootstrapSrc = (0, import_path6.join)(__dirname, "../vc-init.go");
|
|
17204
|
+
const utilsSrc = (0, import_path6.join)(__dirname, "../vc-utils.go");
|
|
17204
17205
|
(0, import_build_utils3.debug)(`Building bootstrap wrapper: ${bootstrapSrc} -> ${bootstrapPath}`);
|
|
17205
17206
|
try {
|
|
17206
17207
|
const bootstrapBuildDir = await (0, import_build_utils3.getWriteableDirectory)();
|
|
17207
17208
|
const bootstrapGoFile = (0, import_path6.join)(bootstrapBuildDir, "main.go");
|
|
17208
17209
|
await (0, import_fs_extra3.copy)(bootstrapSrc, bootstrapGoFile);
|
|
17210
|
+
await (0, import_fs_extra3.copy)(utilsSrc, (0, import_path6.join)(bootstrapBuildDir, "vc-utils.go"));
|
|
17209
17211
|
const bootstrapGoMod = (0, import_path6.join)(bootstrapBuildDir, "go.mod");
|
|
17210
17212
|
await (0, import_fs_extra3.writeFile)(bootstrapGoMod, "module vc-init\n\ngo 1.21\n");
|
|
17211
17213
|
const bootstrapGo = await createGo({
|
|
@@ -17256,12 +17258,17 @@ async function startStandaloneDevServer(opts, resolvedEntrypoint) {
|
|
|
17256
17258
|
PORT: String(port)
|
|
17257
17259
|
});
|
|
17258
17260
|
const runTarget = resolvedEntrypoint === "main.go" ? "." : "./" + (0, import_path6.dirname)(resolvedEntrypoint);
|
|
17261
|
+
const devWrapper = (0, import_path6.join)(__dirname, "../vc-init-dev.go");
|
|
17262
|
+
const devUtils = (0, import_path6.join)(__dirname, "../vc-utils.go");
|
|
17259
17263
|
(0, import_build_utils3.debug)(
|
|
17260
|
-
`Starting standalone Go dev server: go run ${runTarget}
|
|
17264
|
+
`Starting standalone Go dev server wrapper: go run ${devWrapper} (target ${runTarget}, port ${port})`
|
|
17261
17265
|
);
|
|
17262
|
-
const child = (0, import_child_process.spawn)("go", ["run",
|
|
17266
|
+
const child = (0, import_child_process.spawn)("go", ["run", "-tags", "vcdev", devWrapper, devUtils], {
|
|
17263
17267
|
cwd: workPath,
|
|
17264
|
-
env
|
|
17268
|
+
env: {
|
|
17269
|
+
...env,
|
|
17270
|
+
__VC_GO_DEV_RUN_TARGET: runTarget
|
|
17271
|
+
},
|
|
17265
17272
|
stdio: ["ignore", "pipe", "pipe"]
|
|
17266
17273
|
});
|
|
17267
17274
|
child.stdout?.on("data", (data) => {
|
|
@@ -17280,7 +17287,25 @@ async function startStandaloneDevServer(opts, resolvedEntrypoint) {
|
|
|
17280
17287
|
process.stderr.write(chunk.toString());
|
|
17281
17288
|
}
|
|
17282
17289
|
});
|
|
17283
|
-
await new Promise((resolve) =>
|
|
17290
|
+
await new Promise((resolve, reject) => {
|
|
17291
|
+
const timeout = setTimeout(() => {
|
|
17292
|
+
cleanup();
|
|
17293
|
+
resolve();
|
|
17294
|
+
}, 2e3);
|
|
17295
|
+
const onExit = (code2, signal) => {
|
|
17296
|
+
cleanup();
|
|
17297
|
+
reject(
|
|
17298
|
+
new Error(
|
|
17299
|
+
`Standalone Go dev server exited before startup completed (code: ${code2}, signal: ${signal})`
|
|
17300
|
+
)
|
|
17301
|
+
);
|
|
17302
|
+
};
|
|
17303
|
+
const cleanup = () => {
|
|
17304
|
+
clearTimeout(timeout);
|
|
17305
|
+
child.removeListener("exit", onExit);
|
|
17306
|
+
};
|
|
17307
|
+
child.once("exit", onExit);
|
|
17308
|
+
});
|
|
17284
17309
|
return {
|
|
17285
17310
|
port,
|
|
17286
17311
|
pid: child.pid
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/go",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"typescript": "4.3.4",
|
|
33
33
|
"xdg-app-paths": "5.1.0",
|
|
34
34
|
"yauzl-promise": "2.1.3",
|
|
35
|
-
"@vercel/build-utils": "13.
|
|
35
|
+
"@vercel/build-utils": "13.5.0"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "node ../../utils/build-builder.mjs",
|
package/vc-init-dev.go
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
//go:build vcdev
|
|
2
|
+
// +build vcdev
|
|
3
|
+
|
|
4
|
+
// vc-init-dev.go - Dev wrapper for standalone Go servers on Vercel
|
|
5
|
+
//
|
|
6
|
+
// This wrapper is used by `vercel dev` for standalone Go mode. It:
|
|
7
|
+
// 1. Starts the user's server (`go run <target>`) on an internal port
|
|
8
|
+
// 2. Proxies requests from the external dev port to the internal server
|
|
9
|
+
// 3. Optionally strips generated services route prefixes before forwarding
|
|
10
|
+
|
|
11
|
+
package main
|
|
12
|
+
|
|
13
|
+
import (
|
|
14
|
+
"context"
|
|
15
|
+
"fmt"
|
|
16
|
+
"net/http"
|
|
17
|
+
"net/http/httputil"
|
|
18
|
+
"net/url"
|
|
19
|
+
"os"
|
|
20
|
+
"os/exec"
|
|
21
|
+
"os/signal"
|
|
22
|
+
"strconv"
|
|
23
|
+
"strings"
|
|
24
|
+
"sync"
|
|
25
|
+
"syscall"
|
|
26
|
+
"time"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
func mustEnv(name string) string {
|
|
30
|
+
value := strings.TrimSpace(os.Getenv(name))
|
|
31
|
+
if value == "" {
|
|
32
|
+
fmt.Fprintf(os.Stderr, "%s is not set\n", name)
|
|
33
|
+
os.Exit(1)
|
|
34
|
+
}
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func withEnvOverride(env []string, key, value string) []string {
|
|
39
|
+
prefix := key + "="
|
|
40
|
+
filtered := make([]string, 0, len(env)+1)
|
|
41
|
+
for _, item := range env {
|
|
42
|
+
if !strings.HasPrefix(item, prefix) {
|
|
43
|
+
filtered = append(filtered, item)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
filtered = append(filtered, prefix+value)
|
|
47
|
+
return filtered
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func main() {
|
|
51
|
+
listenPortRaw := mustEnv("PORT")
|
|
52
|
+
listenPort, err := strconv.Atoi(listenPortRaw)
|
|
53
|
+
if err != nil || listenPort <= 0 {
|
|
54
|
+
fmt.Fprintf(os.Stderr, "Invalid PORT value: %q\n", listenPortRaw)
|
|
55
|
+
os.Exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
runTarget := mustEnv("__VC_GO_DEV_RUN_TARGET")
|
|
59
|
+
serviceRoutePrefix := resolveServiceRoutePrefix()
|
|
60
|
+
|
|
61
|
+
userPort, err := findFreePort()
|
|
62
|
+
if err != nil {
|
|
63
|
+
fmt.Fprintf(os.Stderr, "Failed to find free port: %v\n", err)
|
|
64
|
+
os.Exit(1)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
68
|
+
defer cancel()
|
|
69
|
+
|
|
70
|
+
cmd := exec.CommandContext(ctx, "go", "run", runTarget)
|
|
71
|
+
cmd.Env = withEnvOverride(os.Environ(), "PORT", strconv.Itoa(userPort))
|
|
72
|
+
cmd.Stdout = os.Stdout
|
|
73
|
+
cmd.Stderr = os.Stderr
|
|
74
|
+
|
|
75
|
+
if err := cmd.Start(); err != nil {
|
|
76
|
+
fmt.Fprintf(os.Stderr, "Failed to start Go dev server: %v\n", err)
|
|
77
|
+
os.Exit(1)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if err := waitForServer(userPort, 30*time.Second); err != nil {
|
|
81
|
+
fmt.Fprintf(os.Stderr, "Go dev server failed to start: %v\n", err)
|
|
82
|
+
_ = cmd.Process.Kill()
|
|
83
|
+
os.Exit(1)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
targetURL, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", userPort))
|
|
87
|
+
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
|
88
|
+
originalDirector := proxy.Director
|
|
89
|
+
proxy.Director = func(req *http.Request) {
|
|
90
|
+
originalDirector(req)
|
|
91
|
+
if host := req.Header.Get("X-Forwarded-Host"); host != "" {
|
|
92
|
+
req.Host = host
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
server := &http.Server{
|
|
97
|
+
Addr: fmt.Sprintf("127.0.0.1:%d", listenPort),
|
|
98
|
+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
99
|
+
if r.URL.Path == "/_vercel/ping" {
|
|
100
|
+
w.WriteHeader(http.StatusOK)
|
|
101
|
+
w.Write([]byte("OK"))
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if r.URL != nil {
|
|
106
|
+
originalPath := r.URL.Path
|
|
107
|
+
r.URL.Path = stripServiceRoutePrefix(r.URL.Path, serviceRoutePrefix)
|
|
108
|
+
if r.URL.Path != originalPath {
|
|
109
|
+
r.URL.RawPath = ""
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
proxy.ServeHTTP(w, r)
|
|
114
|
+
}),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var closeOnce sync.Once
|
|
118
|
+
stop := func() {
|
|
119
|
+
closeOnce.Do(func() {
|
|
120
|
+
cancel()
|
|
121
|
+
_ = server.Close()
|
|
122
|
+
if cmd.Process != nil {
|
|
123
|
+
_ = cmd.Process.Kill()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
go func() {
|
|
129
|
+
_ = cmd.Wait()
|
|
130
|
+
stop()
|
|
131
|
+
}()
|
|
132
|
+
|
|
133
|
+
sigCh := make(chan os.Signal, 1)
|
|
134
|
+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
135
|
+
go func() {
|
|
136
|
+
<-sigCh
|
|
137
|
+
stop()
|
|
138
|
+
}()
|
|
139
|
+
|
|
140
|
+
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
141
|
+
fmt.Fprintf(os.Stderr, "Dev proxy server error: %v\n", err)
|
|
142
|
+
stop()
|
|
143
|
+
os.Exit(1)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
stop()
|
|
147
|
+
}
|
package/vc-init.go
CHANGED
|
@@ -94,34 +94,9 @@ func connectIPC() error {
|
|
|
94
94
|
return nil
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
func findFreePort() (int, error) {
|
|
98
|
-
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
99
|
-
if err != nil {
|
|
100
|
-
return 0, err
|
|
101
|
-
}
|
|
102
|
-
port := listener.Addr().(*net.TCPAddr).Port
|
|
103
|
-
listener.Close()
|
|
104
|
-
return port, nil
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
func waitForServer(port int, timeout time.Duration) error {
|
|
108
|
-
deadline := time.Now().Add(timeout)
|
|
109
|
-
url := fmt.Sprintf("http://127.0.0.1:%d/", port)
|
|
110
|
-
|
|
111
|
-
for time.Now().Before(deadline) {
|
|
112
|
-
resp, err := http.Get(url)
|
|
113
|
-
if err == nil {
|
|
114
|
-
resp.Body.Close()
|
|
115
|
-
return nil
|
|
116
|
-
}
|
|
117
|
-
time.Sleep(50 * time.Millisecond)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return fmt.Errorf("server did not start within %v", timeout)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
97
|
func main() {
|
|
124
98
|
startTime = time.Now()
|
|
99
|
+
serviceRoutePrefix := resolveServiceRoutePrefix()
|
|
125
100
|
|
|
126
101
|
// Connect to IPC socket
|
|
127
102
|
if err := connectIPC(); err != nil {
|
|
@@ -202,6 +177,15 @@ func main() {
|
|
|
202
177
|
}
|
|
203
178
|
}
|
|
204
179
|
|
|
180
|
+
if r.URL != nil {
|
|
181
|
+
originalPath := r.URL.Path
|
|
182
|
+
r.URL.Path = stripServiceRoutePrefix(r.URL.Path, serviceRoutePrefix)
|
|
183
|
+
if r.URL.Path != originalPath {
|
|
184
|
+
// Keep URL path encoding fields consistent after rewrite.
|
|
185
|
+
r.URL.RawPath = ""
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
205
189
|
// Forward request to user's server
|
|
206
190
|
proxy.ServeHTTP(w, r)
|
|
207
191
|
|
|
@@ -251,9 +235,3 @@ func main() {
|
|
|
251
235
|
// Clean up
|
|
252
236
|
cmd.Process.Kill()
|
|
253
237
|
}
|
|
254
|
-
|
|
255
|
-
// Ensure we close IPC connection on exit
|
|
256
|
-
func init() {
|
|
257
|
-
// Note: Using a deferred cleanup in main() is preferred,
|
|
258
|
-
// but this provides a fallback
|
|
259
|
-
}
|
package/vc-utils.go
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"net"
|
|
6
|
+
"net/http"
|
|
7
|
+
"os"
|
|
8
|
+
"strings"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func findFreePort() (int, error) {
|
|
13
|
+
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
14
|
+
if err != nil {
|
|
15
|
+
return 0, err
|
|
16
|
+
}
|
|
17
|
+
port := listener.Addr().(*net.TCPAddr).Port
|
|
18
|
+
listener.Close()
|
|
19
|
+
return port, nil
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func waitForServer(port int, timeout time.Duration) error {
|
|
23
|
+
deadline := time.Now().Add(timeout)
|
|
24
|
+
url := fmt.Sprintf("http://127.0.0.1:%d/", port)
|
|
25
|
+
|
|
26
|
+
for time.Now().Before(deadline) {
|
|
27
|
+
resp, err := http.Get(url)
|
|
28
|
+
if err == nil {
|
|
29
|
+
resp.Body.Close()
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
time.Sleep(50 * time.Millisecond)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return fmt.Errorf("server did not start within %v", timeout)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func normalizeServiceRoutePrefix(rawPrefix string) string {
|
|
39
|
+
if rawPrefix == "" {
|
|
40
|
+
return ""
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
prefix := strings.TrimSpace(rawPrefix)
|
|
44
|
+
if prefix == "" {
|
|
45
|
+
return ""
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if !strings.HasPrefix(prefix, "/") {
|
|
49
|
+
prefix = "/" + prefix
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if prefix != "/" {
|
|
53
|
+
prefix = strings.TrimRight(prefix, "/")
|
|
54
|
+
if prefix == "" {
|
|
55
|
+
prefix = "/"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if prefix == "/" {
|
|
60
|
+
return ""
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return prefix
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func serviceRoutePrefixStripEnabled() bool {
|
|
67
|
+
raw := strings.TrimSpace(os.Getenv("VERCEL_SERVICE_ROUTE_PREFIX_STRIP"))
|
|
68
|
+
if raw == "" {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
normalized := strings.ToLower(raw)
|
|
73
|
+
return normalized == "1" || normalized == "true"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func resolveServiceRoutePrefix() string {
|
|
77
|
+
if !serviceRoutePrefixStripEnabled() {
|
|
78
|
+
return ""
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return normalizeServiceRoutePrefix(os.Getenv("VERCEL_SERVICE_ROUTE_PREFIX"))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func stripServiceRoutePrefix(pathValue string, prefix string) string {
|
|
85
|
+
if pathValue == "*" {
|
|
86
|
+
return pathValue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
normalizedPath := pathValue
|
|
90
|
+
if normalizedPath == "" {
|
|
91
|
+
normalizedPath = "/"
|
|
92
|
+
} else if !strings.HasPrefix(normalizedPath, "/") {
|
|
93
|
+
normalizedPath = "/" + normalizedPath
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if prefix == "" {
|
|
97
|
+
return normalizedPath
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if normalizedPath == prefix {
|
|
101
|
+
return "/"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
prefixWithSlash := prefix + "/"
|
|
105
|
+
if strings.HasPrefix(normalizedPath, prefixWithSlash) {
|
|
106
|
+
stripped := normalizedPath[len(prefix):]
|
|
107
|
+
if stripped == "" {
|
|
108
|
+
return "/"
|
|
109
|
+
}
|
|
110
|
+
return stripped
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return normalizedPath
|
|
114
|
+
}
|