@vercel/ruby 2.3.0 → 2.3.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 CHANGED
@@ -7232,10 +7232,14 @@ function createDevRubyShim(workPath, entrypoint) {
7232
7232
  const vercelRubyDir = (0, import_path2.join)(workPath, ".vercel", "ruby");
7233
7233
  (0, import_fs.mkdirSync)(vercelRubyDir, { recursive: true });
7234
7234
  const shimPath = (0, import_path2.join)(vercelRubyDir, `vc_init_dev.rb`);
7235
+ const utilsPath = (0, import_path2.join)(vercelRubyDir, "vc__utils__ruby.rb");
7235
7236
  const templatePath = (0, import_path2.join)(__dirname, "..", "vc_init_dev.rb");
7237
+ const utilsTemplatePath = (0, import_path2.join)(__dirname, "..", "vc_utils.rb");
7236
7238
  const template = (0, import_fs.readFileSync)(templatePath, "utf8");
7239
+ const utilsTemplate = (0, import_fs.readFileSync)(utilsTemplatePath, "utf8");
7237
7240
  const shimSource = template.replace(/__VC_DEV_ENTRYPOINT__/g, entrypoint);
7238
7241
  (0, import_fs.writeFileSync)(shimPath, shimSource, "utf8");
7242
+ (0, import_fs.writeFileSync)(utilsPath, utilsTemplate, "utf8");
7239
7243
  (0, import_build_utils2.debug)(`Prepared Ruby dev shim at ${shimPath}`);
7240
7244
  return shimPath;
7241
7245
  } catch (err) {
@@ -7260,8 +7264,22 @@ async function run(cmd, args, opts) {
7260
7264
  env: opts.env,
7261
7265
  stdio: ["ignore", "pipe", "pipe"]
7262
7266
  });
7263
- child.stdout?.on("data", (buf) => process.stdout.write(buf));
7264
- child.stderr?.on("data", (buf) => process.stderr.write(buf));
7267
+ child.stdout?.on("data", (data) => {
7268
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
7269
+ if (opts.onStdout) {
7270
+ opts.onStdout(chunk);
7271
+ } else {
7272
+ process.stdout.write(chunk.toString());
7273
+ }
7274
+ });
7275
+ child.stderr?.on("data", (data) => {
7276
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
7277
+ if (opts.onStderr) {
7278
+ opts.onStderr(chunk);
7279
+ } else {
7280
+ process.stderr.write(chunk.toString());
7281
+ }
7282
+ });
7265
7283
  child.on("close", (code, signal) => resolve({ code, signal }));
7266
7284
  });
7267
7285
  }
@@ -7329,7 +7347,12 @@ var startDevServer = async (opts) => {
7329
7347
  const check = await run(
7330
7348
  bundlerPath,
7331
7349
  ["check", "--gemfile", gemfile],
7332
- { cwd: projectDir, env }
7350
+ {
7351
+ cwd: projectDir,
7352
+ env,
7353
+ onStdout: opts.onStdout,
7354
+ onStderr: opts.onStderr
7355
+ }
7333
7356
  );
7334
7357
  if (check.code !== 0) {
7335
7358
  return false;
@@ -7356,21 +7379,21 @@ var startDevServer = async (opts) => {
7356
7379
  const child = (0, import_child_process2.spawn)(cmd, args, {
7357
7380
  cwd: workPath,
7358
7381
  env,
7359
- stdio: ["inherit", "pipe", "pipe"]
7382
+ stdio: ["ignore", "pipe", "pipe"]
7360
7383
  });
7361
7384
  childProcess = child;
7362
7385
  stdoutLogListener = (buf) => {
7363
- const s = buf.toString();
7364
- for (const line of s.split(/\r?\n/)) {
7365
- if (line)
7366
- process.stdout.write(line.endsWith("\n") ? line : line + "\n");
7386
+ if (opts.onStdout) {
7387
+ opts.onStdout(buf);
7388
+ } else {
7389
+ process.stdout.write(buf.toString());
7367
7390
  }
7368
7391
  };
7369
7392
  stderrLogListener = (buf) => {
7370
- const s = buf.toString();
7371
- for (const line of s.split(/\r?\n/)) {
7372
- if (line)
7373
- process.stderr.write(line.endsWith("\n") ? line : line + "\n");
7393
+ if (opts.onStderr) {
7394
+ opts.onStderr(buf);
7395
+ } else {
7396
+ process.stderr.write(buf.toString());
7374
7397
  }
7375
7398
  };
7376
7399
  child.stdout?.on("data", stdoutLogListener);
@@ -7597,6 +7620,8 @@ var build = async ({
7597
7620
  }
7598
7621
  const originalRbPath = (0, import_path3.join)(__dirname, "..", "vc_init.rb");
7599
7622
  const originalHandlerRbContents = await (0, import_fs_extra.readFile)(originalRbPath, "utf8");
7623
+ const originalUtilsRbPath = (0, import_path3.join)(__dirname, "..", "vc_utils.rb");
7624
+ const originalUtilsRbContents = await (0, import_fs_extra.readFile)(originalUtilsRbPath, "utf8");
7600
7625
  (0, import_build_utils3.debug)("entrypoint is", entrypoint);
7601
7626
  const userHandlerFilePath = entrypoint.replace(/\.rb$/, "");
7602
7627
  const nowHandlerRbContents = originalHandlerRbContents.replace(
@@ -7604,6 +7629,7 @@ var build = async ({
7604
7629
  userHandlerFilePath
7605
7630
  );
7606
7631
  const handlerRbFilename = "vc__handler__ruby";
7632
+ const utilsRbFilename = "vc__utils__ruby.rb";
7607
7633
  const predefinedExcludes = [
7608
7634
  ".git/**",
7609
7635
  ".gitignore",
@@ -7621,6 +7647,9 @@ var build = async ({
7621
7647
  outputFiles[`${handlerRbFilename}.rb`] = new import_build_utils3.FileBlob({
7622
7648
  data: nowHandlerRbContents
7623
7649
  });
7650
+ outputFiles[utilsRbFilename] = new import_build_utils3.FileBlob({
7651
+ data: originalUtilsRbContents
7652
+ });
7624
7653
  if (config && (config.includeFiles || config.excludeFiles)) {
7625
7654
  const includedPaths = await matchPaths(config.includeFiles, workPath);
7626
7655
  const excludedPaths = await matchPaths(
@@ -7631,7 +7660,7 @@ var build = async ({
7631
7660
  if (includedPaths.includes(excludedPaths[i])) {
7632
7661
  continue;
7633
7662
  }
7634
- if (excludedPaths[i] === `${handlerRbFilename}.rb`) {
7663
+ if (excludedPaths[i] === `${handlerRbFilename}.rb` || excludedPaths[i] === utilsRbFilename) {
7635
7664
  continue;
7636
7665
  }
7637
7666
  if (excludedPaths[i].startsWith(vendorPath)) {
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.3.0",
4
+ "version": "2.3.2",
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",
@@ -24,7 +25,7 @@
24
25
  "jest-junit": "16.0.0",
25
26
  "semver": "6.3.1",
26
27
  "which": "3.0.0",
27
- "@vercel/build-utils": "13.3.3"
28
+ "@vercel/build-utils": "13.5.0"
28
29
  },
29
30
  "scripts": {
30
31
  "build": "node ../../utils/build-builder.mjs",
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
- def rack_handler(httpMethod, path, body, headers)
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
- req_path = env['PATH_INFO'] || '/'
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(env)
64
+ return @file_server.call(effective_env)
47
65
  end
48
66
 
49
- @app.call(env)
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