@vercel/go 3.4.1 → 3.4.3
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 +38 -13
- package/package.json +3 -3
- package/vc-init-dev.go +147 -0
- package/vc-init.go +10 -32
- package/vc-utils.go +114 -0
package/dist/index.js
CHANGED
|
@@ -8495,12 +8495,12 @@ var require_lib4 = __commonJS({
|
|
|
8495
8495
|
const dest = new URL$1(destination).hostname;
|
|
8496
8496
|
return orig === dest || orig[orig.length - dest.length - 1] === "." && orig.endsWith(dest);
|
|
8497
8497
|
};
|
|
8498
|
-
function
|
|
8499
|
-
if (!
|
|
8498
|
+
function fetch(url, opts) {
|
|
8499
|
+
if (!fetch.Promise) {
|
|
8500
8500
|
throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
|
|
8501
8501
|
}
|
|
8502
|
-
Body.Promise =
|
|
8503
|
-
return new
|
|
8502
|
+
Body.Promise = fetch.Promise;
|
|
8503
|
+
return new fetch.Promise(function(resolve, reject) {
|
|
8504
8504
|
const request = new Request(url, opts);
|
|
8505
8505
|
const options = getNodeRequestOptions(request);
|
|
8506
8506
|
const send = (options.protocol === "https:" ? https : http).request;
|
|
@@ -8550,7 +8550,7 @@ var require_lib4 = __commonJS({
|
|
|
8550
8550
|
req.on("response", function(res) {
|
|
8551
8551
|
clearTimeout(reqTimeout);
|
|
8552
8552
|
const headers = createHeadersLenient(res.headers);
|
|
8553
|
-
if (
|
|
8553
|
+
if (fetch.isRedirect(res.statusCode)) {
|
|
8554
8554
|
const location = headers.get("Location");
|
|
8555
8555
|
let locationURL = null;
|
|
8556
8556
|
try {
|
|
@@ -8612,7 +8612,7 @@ var require_lib4 = __commonJS({
|
|
|
8612
8612
|
requestOpts.body = void 0;
|
|
8613
8613
|
requestOpts.headers.delete("content-length");
|
|
8614
8614
|
}
|
|
8615
|
-
resolve(
|
|
8615
|
+
resolve(fetch(new Request(locationURL, requestOpts)));
|
|
8616
8616
|
finalize();
|
|
8617
8617
|
return;
|
|
8618
8618
|
}
|
|
@@ -8672,11 +8672,11 @@ var require_lib4 = __commonJS({
|
|
|
8672
8672
|
writeToStream(req, request);
|
|
8673
8673
|
});
|
|
8674
8674
|
}
|
|
8675
|
-
|
|
8675
|
+
fetch.isRedirect = function(code2) {
|
|
8676
8676
|
return code2 === 301 || code2 === 302 || code2 === 303 || code2 === 307 || code2 === 308;
|
|
8677
8677
|
};
|
|
8678
|
-
|
|
8679
|
-
module2.exports = exports =
|
|
8678
|
+
fetch.Promise = global.Promise;
|
|
8679
|
+
module2.exports = exports = fetch;
|
|
8680
8680
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8681
8681
|
exports.default = exports;
|
|
8682
8682
|
exports.Headers = Headers;
|
|
@@ -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.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index",
|
|
6
6
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"node-fetch": "^2.2.1",
|
|
30
30
|
"string-argv": "0.3.1",
|
|
31
31
|
"tar": "7.5.7",
|
|
32
|
-
"typescript": "4.
|
|
32
|
+
"typescript": "4.9.5",
|
|
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.6.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
|
+
}
|