@vercel/ruby 2.4.0-canary.20260211174907.cdd2da6 → 2.4.0
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 +566 -347
- package/package.json +9 -6
- package/vc_init.rb +12 -2
- package/vc_init_dev.rb +21 -3
- package/vc_utils.rb +101 -0
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/ruby",
|
|
3
3
|
"author": "Nathan Cahill <nathan@nathancahill.com>",
|
|
4
|
-
"version": "2.4.0
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index",
|
|
7
7
|
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
10
10
|
"vc_init.rb",
|
|
11
|
-
"vc_init_dev.rb"
|
|
11
|
+
"vc_init_dev.rb",
|
|
12
|
+
"vc_utils.rb"
|
|
12
13
|
],
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
@@ -21,15 +22,17 @@
|
|
|
21
22
|
"@types/which": "3.0.0",
|
|
22
23
|
"execa": "2.0.4",
|
|
23
24
|
"fs-extra": "^7.0.1",
|
|
24
|
-
"jest-junit": "16.0.0",
|
|
25
25
|
"semver": "6.3.1",
|
|
26
|
+
"vitest": "2.0.3",
|
|
26
27
|
"which": "3.0.0",
|
|
27
|
-
"@vercel/build-utils": "13.
|
|
28
|
+
"@vercel/build-utils": "13.26.2"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
30
31
|
"build": "node ../../utils/build-builder.mjs",
|
|
31
|
-
"test": "
|
|
32
|
+
"test": "vitest run --config ../../vitest.config.mts",
|
|
32
33
|
"test-e2e": "pnpm test",
|
|
33
|
-
"type-check": "tsc --noEmit"
|
|
34
|
+
"type-check": "tsc --noEmit",
|
|
35
|
+
"vitest-run": "vitest -c ../../vitest.config.mts",
|
|
36
|
+
"vitest-e2e": "glob --absolute 'test/**/*.test.js' 'test/**/*.test.ts' 'tests/**/*.test.js' 'tests/**/*.test.ts'"
|
|
34
37
|
}
|
|
35
38
|
}
|
package/vc_init.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'webrick'
|
|
|
3
3
|
require 'net/http'
|
|
4
4
|
require 'base64'
|
|
5
5
|
require 'json'
|
|
6
|
+
require_relative 'vc__utils__ruby'
|
|
6
7
|
|
|
7
8
|
$entrypoint = '__VC_HANDLER_FILENAME'
|
|
8
9
|
|
|
@@ -10,13 +11,17 @@ ENV['RAILS_ENV'] ||= 'production'
|
|
|
10
11
|
ENV['RACK_ENV'] ||= 'production'
|
|
11
12
|
ENV['RAILS_LOG_TO_STDOUT'] ||= '1'
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
$service_route_prefix = resolve_service_route_prefix
|
|
16
|
+
|
|
17
|
+
def rack_handler(httpMethod, path, body, headers, script_name = '')
|
|
14
18
|
require 'rack'
|
|
15
19
|
|
|
16
20
|
app, _ = Rack::Builder.parse_file($entrypoint)
|
|
17
21
|
server = Rack::MockRequest.new app
|
|
18
22
|
|
|
19
23
|
env = headers.transform_keys { |k| k.split('-').join('_').prepend('HTTP_').upcase }
|
|
24
|
+
env['SCRIPT_NAME'] = script_name unless script_name.empty?
|
|
20
25
|
res = server.request(httpMethod, path, env.merge({ :input => body }))
|
|
21
26
|
|
|
22
27
|
{
|
|
@@ -96,8 +101,13 @@ def vc__handler(event:, context:)
|
|
|
96
101
|
body = Base64.decode64(body)
|
|
97
102
|
end
|
|
98
103
|
|
|
104
|
+
path, script_name = apply_service_route_prefix_to_target(
|
|
105
|
+
path,
|
|
106
|
+
$service_route_prefix
|
|
107
|
+
)
|
|
108
|
+
|
|
99
109
|
if $entrypoint.end_with? '.ru'
|
|
100
|
-
return rack_handler(httpMethod, path, body, headers)
|
|
110
|
+
return rack_handler(httpMethod, path, body, headers, script_name)
|
|
101
111
|
end
|
|
102
112
|
|
|
103
113
|
return webrick_handler(httpMethod, path, body, headers)
|
package/vc_init_dev.rb
CHANGED
|
@@ -10,12 +10,14 @@ require 'rack'
|
|
|
10
10
|
require 'rack/handler/webrick'
|
|
11
11
|
require 'webrick'
|
|
12
12
|
require 'socket'
|
|
13
|
+
require_relative 'vc__utils__ruby'
|
|
13
14
|
|
|
14
15
|
$stdout.sync = true
|
|
15
16
|
$stderr.sync = true
|
|
16
17
|
|
|
17
18
|
USER_ENTRYPOINT = "__VC_DEV_ENTRYPOINT__"
|
|
18
19
|
PUBLIC_DIR = 'public'
|
|
20
|
+
SERVICE_ROUTE_PREFIX = resolve_service_route_prefix
|
|
19
21
|
|
|
20
22
|
def build_user_app
|
|
21
23
|
if USER_ENTRYPOINT.end_with?('.ru')
|
|
@@ -36,17 +38,33 @@ class StaticThenApp
|
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def call(env)
|
|
39
|
-
|
|
41
|
+
effective_env = env.dup
|
|
42
|
+
req_path, matched_prefix = strip_service_route_prefix(
|
|
43
|
+
effective_env['PATH_INFO'] || '/',
|
|
44
|
+
SERVICE_ROUTE_PREFIX
|
|
45
|
+
)
|
|
46
|
+
effective_env['PATH_INFO'] = req_path
|
|
47
|
+
|
|
48
|
+
unless matched_prefix.empty?
|
|
49
|
+
script_name = effective_env['SCRIPT_NAME'] || ''
|
|
50
|
+
if !script_name.empty? && script_name != '/'
|
|
51
|
+
script_name = script_name.sub(%r{/+\z}, '')
|
|
52
|
+
else
|
|
53
|
+
script_name = ''
|
|
54
|
+
end
|
|
55
|
+
effective_env['SCRIPT_NAME'] = "#{script_name}#{matched_prefix}"
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
# Normalize path and guard against traversal
|
|
41
59
|
safe = req_path.sub(/^\//, '')
|
|
42
60
|
full = File.expand_path(safe, @base)
|
|
43
61
|
|
|
44
62
|
if full.start_with?(@base + File::SEPARATOR) && File.file?(full)
|
|
45
63
|
# Delegate to Rack::File which handles HEAD/GET correctly
|
|
46
|
-
return @file_server.call(
|
|
64
|
+
return @file_server.call(effective_env)
|
|
47
65
|
end
|
|
48
66
|
|
|
49
|
-
@app.call(
|
|
67
|
+
@app.call(effective_env)
|
|
50
68
|
end
|
|
51
69
|
end
|
|
52
70
|
|
package/vc_utils.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
# Normalize a configured service prefix into canonical mount-path form.
|
|
4
|
+
#
|
|
5
|
+
# Returns an empty string when unset/blank/root ("/"), otherwise:
|
|
6
|
+
# - always starts with "/"
|
|
7
|
+
# - has no trailing slash
|
|
8
|
+
def normalize_service_route_prefix(raw_prefix)
|
|
9
|
+
return '' if raw_prefix.nil?
|
|
10
|
+
|
|
11
|
+
prefix = raw_prefix.strip
|
|
12
|
+
return '' if prefix.empty?
|
|
13
|
+
|
|
14
|
+
prefix = "/#{prefix}" unless prefix.start_with?('/')
|
|
15
|
+
|
|
16
|
+
if prefix != '/'
|
|
17
|
+
prefix = prefix.sub(%r{/+\z}, '')
|
|
18
|
+
prefix = '/' if prefix.empty?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
prefix == '/' ? '' : prefix
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Return whether service-prefix stripping is explicitly enabled.
|
|
25
|
+
#
|
|
26
|
+
# Stripping is gated behind a dedicated env var so manually configured
|
|
27
|
+
# route prefixes can be preserved by default.
|
|
28
|
+
def service_route_prefix_strip_enabled?
|
|
29
|
+
raw = ENV['VERCEL_SERVICE_ROUTE_PREFIX_STRIP']
|
|
30
|
+
return false if raw.nil? || raw.empty?
|
|
31
|
+
|
|
32
|
+
%w[1 true].include?(raw.downcase)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_service_route_prefix
|
|
36
|
+
return '' unless service_route_prefix_strip_enabled?
|
|
37
|
+
|
|
38
|
+
normalize_service_route_prefix(ENV['VERCEL_SERVICE_ROUTE_PREFIX'])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Split an HTTP request-target into [path, query].
|
|
42
|
+
#
|
|
43
|
+
# Supports:
|
|
44
|
+
# - origin-form: "/a/b?x=1"
|
|
45
|
+
# - absolute-form: "https://example.com/a/b?x=1"
|
|
46
|
+
# - asterisk-form: "*"
|
|
47
|
+
def split_request_target(target)
|
|
48
|
+
return ['/', ''] if target.nil? || target.empty?
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
parsed = URI.parse(target)
|
|
52
|
+
if parsed.scheme && parsed.host
|
|
53
|
+
path = parsed.path.nil? || parsed.path.empty? ? '/' : parsed.path
|
|
54
|
+
return [path, parsed.query.to_s]
|
|
55
|
+
end
|
|
56
|
+
rescue URI::InvalidURIError
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
return ['*', ''] if target == '*'
|
|
60
|
+
|
|
61
|
+
path, query = target.split('?', 2)
|
|
62
|
+
path = '/' if path.nil? || path.empty?
|
|
63
|
+
path = "/#{path}" unless path.start_with?('/')
|
|
64
|
+
[path, query.to_s]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Strip the configured service route prefix from a request path.
|
|
68
|
+
#
|
|
69
|
+
# Returns:
|
|
70
|
+
# - stripped path passed to the user app
|
|
71
|
+
# - matched mount prefix (empty when no prefix matched)
|
|
72
|
+
def strip_service_route_prefix(path, prefix)
|
|
73
|
+
return [path, ''] if path == '*'
|
|
74
|
+
|
|
75
|
+
normalized_path = path.nil? || path.empty? ? '/' : path
|
|
76
|
+
normalized_path = "/#{normalized_path}" unless normalized_path.start_with?('/')
|
|
77
|
+
|
|
78
|
+
normalized_prefix = prefix.to_s
|
|
79
|
+
return [normalized_path, ''] if normalized_prefix.empty?
|
|
80
|
+
|
|
81
|
+
return ['/', normalized_prefix] if normalized_path == normalized_prefix
|
|
82
|
+
|
|
83
|
+
if normalized_path.start_with?("#{normalized_prefix}/")
|
|
84
|
+
stripped = normalized_path[normalized_prefix.length..]
|
|
85
|
+
return [stripped.nil? || stripped.empty? ? '/' : stripped, normalized_prefix]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
[normalized_path, '']
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Apply service-prefix stripping to a full request-target.
|
|
92
|
+
#
|
|
93
|
+
# Returns:
|
|
94
|
+
# - updated request-target (path + optional query)
|
|
95
|
+
# - matched mount prefix for Rack SCRIPT_NAME (or "")
|
|
96
|
+
def apply_service_route_prefix_to_target(target, prefix)
|
|
97
|
+
path, query = split_request_target(target)
|
|
98
|
+
path, script_name = strip_service_route_prefix(path, prefix)
|
|
99
|
+
updated = query.empty? ? path : "#{path}?#{query}"
|
|
100
|
+
[updated, script_name]
|
|
101
|
+
end
|