vercel-cli 48.6.6__py3-none-any.whl → 50.4.6__py3-none-any.whl

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.
Files changed (34) hide show
  1. vercel_cli/vendor/dist/index.js +70005 -64961
  2. vercel_cli/vendor/dist/vc.js +4 -3
  3. vercel_cli/vendor/node_modules/.package-lock.json +6 -6
  4. vercel_cli/vendor/node_modules/@vercel/build-utils/CHANGELOG.md +132 -0
  5. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/framework-helpers.d.ts +5 -4
  6. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/framework-helpers.js +28 -2
  7. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/fs/node-version.js +8 -3
  8. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/fs/read-config-file.d.ts +6 -0
  9. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/fs/read-config-file.js +11 -0
  10. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/fs/run-user-scripts.d.ts +25 -6
  11. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/fs/run-user-scripts.js +53 -11
  12. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/generate-node-builder-functions.d.ts +8 -2
  13. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/generate-node-builder-functions.js +4 -2
  14. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/index.d.ts +5 -4
  15. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/index.js +2545 -502
  16. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/lambda.d.ts +17 -0
  17. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/lambda.js +11 -1
  18. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/python.d.ts +22 -0
  19. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/python.js +85 -0
  20. vercel_cli/vendor/node_modules/@vercel/build-utils/dist/types.d.ts +9 -0
  21. vercel_cli/vendor/node_modules/@vercel/build-utils/lib/python/ast_parser.py +72 -0
  22. vercel_cli/vendor/node_modules/@vercel/build-utils/lib/python/tests/test_ast_parser.py +72 -0
  23. vercel_cli/vendor/node_modules/@vercel/build-utils/package.json +4 -4
  24. vercel_cli/vendor/node_modules/@vercel/python/dist/index.js +910 -421
  25. vercel_cli/vendor/node_modules/@vercel/python/package.json +3 -3
  26. vercel_cli/vendor/node_modules/@vercel/python/vc_init.py +371 -161
  27. vercel_cli/vendor/node_modules/@vercel/python/vc_init_dev_asgi.py +3 -2
  28. vercel_cli/vendor/package.json +5 -4
  29. {vercel_cli-48.6.6.dist-info → vercel_cli-50.4.6.dist-info}/METADATA +1 -1
  30. {vercel_cli-48.6.6.dist-info → vercel_cli-50.4.6.dist-info}/RECORD +34 -30
  31. {vercel_cli-48.6.6.dist-info → vercel_cli-50.4.6.dist-info}/WHEEL +1 -1
  32. /vercel_cli/vendor/dist/{builder-worker.js → builder-worker.cjs} +0 -0
  33. /vercel_cli/vendor/dist/{get-latest-worker.js → get-latest-worker.cjs} +0 -0
  34. {vercel_cli-48.6.6.dist-info → vercel_cli-50.4.6.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "5.0.10",
3
+ "version": "6.1.6",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -21,12 +21,12 @@
21
21
  "@types/jest": "27.4.1",
22
22
  "@types/node": "14.18.33",
23
23
  "@types/which": "3.0.0",
24
- "@vercel/build-utils": "12.1.3",
25
24
  "cross-env": "7.0.3",
26
25
  "execa": "^1.0.0",
27
26
  "fs-extra": "11.1.1",
28
27
  "jest-junit": "16.0.0",
29
- "which": "3.0.0"
28
+ "which": "3.0.0",
29
+ "@vercel/build-utils": "13.2.4"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "node ../../utils/build-builder.mjs",
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import sys
2
3
  import os
3
4
  import site
@@ -5,9 +6,21 @@ import importlib
5
6
  import base64
6
7
  import json
7
8
  import inspect
9
+ import asyncio
10
+ import http
11
+ import time
12
+ import traceback
8
13
  from importlib import util
9
- from http.server import BaseHTTPRequestHandler
14
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
10
15
  import socket
16
+ import functools
17
+ import logging
18
+ import builtins
19
+ from typing import Callable, Literal, TextIO
20
+ import contextvars
21
+ import contextlib
22
+ import atexit
23
+
11
24
 
12
25
  _here = os.path.dirname(__file__)
13
26
  _vendor_rel = '__VC_HANDLER_VENDOR_DIR'
@@ -30,13 +43,209 @@ if os.path.isdir(_vendor):
30
43
 
31
44
  importlib.invalidate_caches()
32
45
 
46
+
47
+ def setup_logging(send_message: Callable[[dict], None], storage: contextvars.ContextVar[dict | None]):
48
+ # Override logging.Handler to send logs to the platform when a request context is available.
49
+ class VCLogHandler(logging.Handler):
50
+ def emit(self, record: logging.LogRecord):
51
+ try:
52
+ message = record.getMessage()
53
+ except Exception:
54
+ message = repr(getattr(record, "msg", ""))
55
+
56
+ with contextlib.suppress(Exception):
57
+ if record.exc_info:
58
+ # logging allows exc_info=True or a (type, value, tb) tuple
59
+ exc_info = record.exc_info
60
+ if exc_info is True:
61
+ exc_info = sys.exc_info()
62
+ if isinstance(exc_info, tuple):
63
+ tb = ''.join(traceback.format_exception(*exc_info))
64
+ if tb:
65
+ if message:
66
+ message = f"{message}\n{tb}"
67
+ else:
68
+ message = tb
69
+
70
+ if record.levelno >= logging.CRITICAL:
71
+ level = "fatal"
72
+ elif record.levelno >= logging.ERROR:
73
+ level = "error"
74
+ elif record.levelno >= logging.WARNING:
75
+ level = "warn"
76
+ elif record.levelno >= logging.INFO:
77
+ level = "info"
78
+ else:
79
+ level = "debug"
80
+
81
+ context = storage.get()
82
+ if context is not None:
83
+ send_message({
84
+ "type": "log",
85
+ "payload": {
86
+ "context": {
87
+ "invocationId": context['invocationId'],
88
+ "requestId": context['requestId'],
89
+ },
90
+ "message": base64.b64encode(message.encode()).decode(),
91
+ "level": level,
92
+ }
93
+ })
94
+ else:
95
+ # If IPC is not ready, enqueue the message to be sent later.
96
+ enqueue_or_send_message({
97
+ "type": "log",
98
+ "payload": {
99
+ "context": {"invocationId": "0", "requestId": 0},
100
+ "message": base64.b64encode(message.encode()).decode(),
101
+ "level": level,
102
+ }
103
+ })
104
+
105
+ # Override sys.stdout and sys.stderr to map logs to the correct request
106
+ class StreamWrapper:
107
+ def __init__(self, stream: TextIO, stream_name: Literal["stdout", "stderr"]):
108
+ self.stream = stream
109
+ self.stream_name = stream_name
110
+
111
+ def write(self, message: str):
112
+ context = storage.get()
113
+ if context is not None:
114
+ send_message({
115
+ "type": "log",
116
+ "payload": {
117
+ "context": {
118
+ "invocationId": context['invocationId'],
119
+ "requestId": context['requestId'],
120
+ },
121
+ "message": base64.b64encode(message.encode()).decode(),
122
+ "stream": self.stream_name,
123
+ }
124
+ })
125
+ else:
126
+ enqueue_or_send_message({
127
+ "type": "log",
128
+ "payload": {
129
+ "context": {"invocationId": "0", "requestId": 0},
130
+ "message": base64.b64encode(message.encode()).decode(),
131
+ "stream": self.stream_name,
132
+ }
133
+ })
134
+
135
+ def __getattr__(self, name):
136
+ return getattr(self.stream, name)
137
+
138
+ sys.stdout = StreamWrapper(sys.stdout, "stdout")
139
+ sys.stderr = StreamWrapper(sys.stderr, "stderr")
140
+
141
+ logging.basicConfig(level=logging.INFO, handlers=[VCLogHandler()], force=True)
142
+
143
+ # Ensure built-in print funnels through stdout wrapper so prints are
144
+ # attributed to the current request context.
145
+ def print_wrapper(func: Callable[..., None]) -> Callable[..., None]:
146
+ @functools.wraps(func)
147
+ def wrapper(*args, sep=' ', end='\n', file=None, flush=False):
148
+ if file is None:
149
+ file = sys.stdout
150
+ if file in (sys.stdout, sys.stderr):
151
+ file.write(sep.join(map(str, args)) + end)
152
+ if flush:
153
+ file.flush()
154
+ else:
155
+ # User specified a different file, use original print behavior
156
+ func(*args, sep=sep, end=end, file=file, flush=flush)
157
+ return wrapper
158
+
159
+ builtins.print = print_wrapper(builtins.print)
160
+
161
+
162
+ def _stderr(message: str):
163
+ with contextlib.suppress(Exception):
164
+ _original_stderr.write(message + "\n")
165
+ _original_stderr.flush()
166
+
167
+
168
+ # If running in the platform (IPC present), logging must be setup before importing user code so that
169
+ # logs happening outside the request context are emitted correctly.
170
+ ipc_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
171
+ storage: contextvars.ContextVar[dict | None] = contextvars.ContextVar('storage', default=None)
172
+ send_message = lambda m: None
173
+ _original_stderr = sys.stderr
174
+
175
+
176
+ # Buffer for pre-handshake logs (to avoid blocking IPC on startup)
177
+ _ipc_ready = False
178
+ _init_log_buf: list[dict] = []
179
+ _INIT_LOG_BUF_MAX_BYTES = 1_000_000
180
+ _init_log_buf_bytes = 0
181
+
182
+
183
+ def enqueue_or_send_message(msg: dict):
184
+ global _init_log_buf_bytes
185
+ if _ipc_ready:
186
+ send_message(msg)
187
+ return
188
+
189
+ enc_len = len(json.dumps(msg))
190
+
191
+ if _init_log_buf_bytes + enc_len <= _INIT_LOG_BUF_MAX_BYTES:
192
+ _init_log_buf.append(msg)
193
+ _init_log_buf_bytes += enc_len
194
+ else:
195
+ # Fallback so message is not lost if buffer is full
196
+ with contextlib.suppress(Exception):
197
+ payload = msg.get("payload", {})
198
+ decoded = base64.b64decode(payload.get("message", "")).decode(errors="ignore")
199
+ _original_stderr.write(decoded + "\n")
200
+
201
+
202
+ def flush_init_log_buf_to_stderr():
203
+ global _init_log_buf, _init_log_buf_bytes
204
+ try:
205
+ combined: list[str] = []
206
+ for m in _init_log_buf:
207
+ payload = m.get("payload", {})
208
+ msg = payload.get("message")
209
+ if not msg:
210
+ continue
211
+ with contextlib.suppress(Exception):
212
+ decoded = base64.b64decode(msg).decode(errors="ignore")
213
+ combined.append(decoded)
214
+ if combined:
215
+ _stderr("".join(combined))
216
+ except Exception:
217
+ pass
218
+ finally:
219
+ _init_log_buf.clear()
220
+ _init_log_buf_bytes = 0
221
+
222
+
223
+ atexit.register(flush_init_log_buf_to_stderr)
224
+
225
+
226
+ if 'VERCEL_IPC_PATH' in os.environ:
227
+ with contextlib.suppress(Exception):
228
+ ipc_sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
229
+
230
+ def send_message(message: dict):
231
+ with contextlib.suppress(Exception):
232
+ ipc_sock.sendall((json.dumps(message) + '\0').encode())
233
+
234
+ setup_logging(send_message, storage)
235
+
236
+
33
237
  # Import relative path https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
34
- user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
35
- __vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
36
- __vc_module = util.module_from_spec(__vc_spec)
37
- sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
38
- __vc_spec.loader.exec_module(__vc_module)
39
- __vc_variables = dir(__vc_module)
238
+ try:
239
+ user_mod_path = os.path.join(_here, "__VC_HANDLER_ENTRYPOINT") # absolute
240
+ __vc_spec = util.spec_from_file_location("__VC_HANDLER_MODULE_NAME", user_mod_path)
241
+ __vc_module = util.module_from_spec(__vc_spec)
242
+ sys.modules["__VC_HANDLER_MODULE_NAME"] = __vc_module
243
+ __vc_spec.loader.exec_module(__vc_module)
244
+ __vc_variables = dir(__vc_module)
245
+ except Exception:
246
+ _stderr(f'Error importing __VC_HANDLER_ENTRYPOINT:')
247
+ _stderr(traceback.format_exc())
248
+ exit(1)
40
249
 
41
250
  _use_legacy_asyncio = sys.version_info < (3, 10)
42
251
 
@@ -51,21 +260,98 @@ def format_headers(headers, decode=False):
51
260
  keyToList[key].append(value)
52
261
  return keyToList
53
262
 
54
- if 'VERCEL_IPC_PATH' in os.environ:
55
- from http.server import ThreadingHTTPServer
56
- import http
57
- import time
58
- import contextvars
59
- import functools
60
- import builtins
61
- import logging
62
263
 
63
- start_time = time.time()
64
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
65
- sock.connect(os.getenv("VERCEL_IPC_PATH", ""))
264
+ class ASGIMiddleware:
265
+ """
266
+ ASGI middleware that preserves Vercel IPC semantics for request lifecycle:
267
+ - Handles /_vercel/ping
268
+ - Extracts x-vercel-internal-* headers and removes them from downstream app
269
+ - Sets request context into `storage` for logging/metrics
270
+ - Emits handler-started and end IPC messages
271
+ """
272
+ def __init__(self, app):
273
+ self.app = app
274
+
275
+ async def __call__(self, scope, receive, send):
276
+ if scope.get('type') != 'http':
277
+ # Non-HTTP traffic is forwarded verbatim
278
+ await self.app(scope, receive, send)
279
+ return
280
+
281
+ if scope.get('path') == '/_vercel/ping':
282
+ await send({
283
+ 'type': 'http.response.start',
284
+ 'status': 200,
285
+ 'headers': [],
286
+ })
287
+ await send({
288
+ 'type': 'http.response.body',
289
+ 'body': b'',
290
+ 'more_body': False,
291
+ })
292
+ return
293
+
294
+ # Extract internal headers and set per-request context
295
+ headers_list = scope.get('headers', []) or []
296
+ new_headers = []
297
+ invocation_id = "0"
298
+ request_id = 0
299
+
300
+ def _b2s(b: bytes) -> str:
301
+ try:
302
+ return b.decode()
303
+ except Exception:
304
+ return ''
305
+
306
+ for k, v in headers_list:
307
+ key = _b2s(k).lower()
308
+ val = _b2s(v)
309
+ if key == 'x-vercel-internal-invocation-id':
310
+ invocation_id = val
311
+ continue
312
+ if key == 'x-vercel-internal-request-id':
313
+ request_id = int(val) if val.isdigit() else 0
314
+ continue
315
+ if key in ('x-vercel-internal-span-id', 'x-vercel-internal-trace-id'):
316
+ continue
317
+ new_headers.append((k, v))
318
+
319
+ new_scope = dict(scope)
320
+ new_scope['headers'] = new_headers
321
+
322
+ # Announce handler start and set context for logging/metrics
323
+ send_message({
324
+ "type": "handler-started",
325
+ "payload": {
326
+ "handlerStartedAt": int(time.time() * 1000),
327
+ "context": {
328
+ "invocationId": invocation_id,
329
+ "requestId": request_id,
330
+ }
331
+ }
332
+ })
66
333
 
67
- send_message = lambda message: sock.sendall((json.dumps(message) + '\0').encode())
68
- storage = contextvars.ContextVar('storage', default=None)
334
+ token = storage.set({
335
+ "invocationId": invocation_id,
336
+ "requestId": request_id,
337
+ })
338
+
339
+ try:
340
+ await self.app(new_scope, receive, send)
341
+ finally:
342
+ storage.reset(token)
343
+ send_message({
344
+ "type": "end",
345
+ "payload": {
346
+ "context": {
347
+ "invocationId": invocation_id,
348
+ "requestId": request_id,
349
+ }
350
+ }
351
+ })
352
+
353
+ if 'VERCEL_IPC_PATH' in os.environ:
354
+ start_time = time.time()
69
355
 
70
356
  # Override urlopen from urllib3 (& requests) to send Request Metrics
71
357
  try:
@@ -110,71 +396,6 @@ if 'VERCEL_IPC_PATH' in os.environ:
110
396
  except:
111
397
  pass
112
398
 
113
- # Override sys.stdout and sys.stderr to map logs to the correct request
114
- class StreamWrapper:
115
- def __init__(self, stream, stream_name):
116
- self.stream = stream
117
- self.stream_name = stream_name
118
-
119
- def write(self, message):
120
- context = storage.get()
121
- if context is not None:
122
- send_message({
123
- "type": "log",
124
- "payload": {
125
- "context": {
126
- "invocationId": context['invocationId'],
127
- "requestId": context['requestId'],
128
- },
129
- "message": base64.b64encode(message.encode()).decode(),
130
- "stream": self.stream_name,
131
- }
132
- })
133
- else:
134
- self.stream.write(message)
135
-
136
- def __getattr__(self, name):
137
- return getattr(self.stream, name)
138
-
139
- sys.stdout = StreamWrapper(sys.stdout, "stdout")
140
- sys.stderr = StreamWrapper(sys.stderr, "stderr")
141
-
142
- # Override the global print to log to stdout
143
- def print_wrapper(func):
144
- @functools.wraps(func)
145
- def wrapper(*args, **kwargs):
146
- sys.stdout.write(' '.join(map(str, args)) + '\n')
147
- return wrapper
148
- builtins.print = print_wrapper(builtins.print)
149
-
150
- # Override logging to maps logs to the correct request
151
- def logging_wrapper(func, level="info"):
152
- @functools.wraps(func)
153
- def wrapper(*args, **kwargs):
154
- context = storage.get()
155
- if context is not None:
156
- send_message({
157
- "type": "log",
158
- "payload": {
159
- "context": {
160
- "invocationId": context['invocationId'],
161
- "requestId": context['requestId'],
162
- },
163
- "message": base64.b64encode(f"{args[0]}".encode()).decode(),
164
- "level": level,
165
- }
166
- })
167
- else:
168
- func(*args, **kwargs)
169
- return wrapper
170
-
171
- logging.basicConfig(level=logging.INFO)
172
- logging.debug = logging_wrapper(logging.debug)
173
- logging.info = logging_wrapper(logging.info)
174
- logging.warning = logging_wrapper(logging.warning, "warn")
175
- logging.error = logging_wrapper(logging.error, "error")
176
- logging.critical = logging_wrapper(logging.critical, "error")
177
-
178
399
  class BaseHandler(BaseHTTPRequestHandler):
179
400
  # Re-implementation of BaseHTTPRequestHandler's log_message method to
180
401
  # log to stdout instead of stderr.
@@ -240,8 +461,8 @@ if 'VERCEL_IPC_PATH' in os.environ:
240
461
  if 'handler' in __vc_variables or 'Handler' in __vc_variables:
241
462
  base = __vc_module.handler if ('handler' in __vc_variables) else __vc_module.Handler
242
463
  if not issubclass(base, BaseHTTPRequestHandler):
243
- print('Handler must inherit from BaseHTTPRequestHandler')
244
- print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
464
+ _stderr('Handler must inherit from BaseHTTPRequestHandler')
465
+ _stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
245
466
  exit(1)
246
467
 
247
468
  class Handler(BaseHandler, base):
@@ -322,80 +543,53 @@ if 'VERCEL_IPC_PATH' in os.environ:
322
543
  if hasattr(response, 'close'):
323
544
  response.close()
324
545
  else:
325
- from urllib.parse import urlparse
326
- from io import BytesIO
327
- import asyncio
328
-
329
- app = __vc_module.app
330
-
331
- class Handler(BaseHandler):
332
- def handle_request(self):
333
- # Prepare ASGI scope
334
- url = urlparse(self.path)
335
- headers_encoded = []
336
- for k, v in self.headers.items():
337
- # Cope with repeated headers in the encoding.
338
- if isinstance(v, list):
339
- headers_encoded.append([k.lower().encode(), [i.encode() for i in v]])
340
- else:
341
- headers_encoded.append([k.lower().encode(), v.encode()])
342
- scope = {
343
- 'server': (self.headers.get('host', 'lambda'), self.headers.get('x-forwarded-port', 80)),
344
- 'client': (self.headers.get(
345
- 'x-forwarded-for', self.headers.get(
346
- 'x-real-ip')), 0),
347
- 'scheme': self.headers.get('x-forwarded-proto', 'http'),
348
- 'root_path': '',
349
- 'query_string': url.query.encode(),
350
- 'headers': headers_encoded,
351
- 'type': 'http',
352
- 'http_version': '1.1',
353
- 'method': self.command,
354
- 'path': url.path,
355
- 'raw_path': url.path.encode(),
356
- }
546
+ # ASGI: Run with Uvicorn so we get proper lifespan and protocol handling
547
+ try:
548
+ import uvicorn
549
+ except Exception:
550
+ _stderr('Uvicorn is required to run ASGI apps. Please ensure it is installed.')
551
+ exit(1)
552
+
553
+ # Prefer a callable app.asgi when available; some frameworks expose a boolean here
554
+ user_app_candidate = getattr(__vc_module.app, 'asgi', None)
555
+ user_app = user_app_candidate if callable(user_app_candidate) else __vc_module.app
556
+ asgi_app = ASGIMiddleware(user_app)
557
+
558
+ # Pre-bind a socket to obtain an ephemeral port for IPC announcement
559
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
560
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
561
+ sock.bind(('127.0.0.1', 0))
562
+ sock.listen(2048)
563
+ http_port = sock.getsockname()[1]
564
+
565
+ config = uvicorn.Config(
566
+ app=asgi_app,
567
+ fd=sock.fileno(),
568
+ lifespan='auto',
569
+ access_log=False,
570
+ log_config=None,
571
+ log_level='warning',
572
+ )
573
+ server = uvicorn.Server(config)
357
574
 
358
- if 'content-length' in self.headers:
359
- content_length = int(self.headers['content-length'])
360
- body = self.rfile.read(content_length)
361
- else:
362
- body = b''
575
+ send_message({
576
+ "type": "server-started",
577
+ "payload": {
578
+ "initDuration": int((time.time() - start_time) * 1000),
579
+ "httpPort": http_port,
580
+ }
581
+ })
363
582
 
364
- if _use_legacy_asyncio:
365
- loop = asyncio.new_event_loop()
366
- app_queue = asyncio.Queue(loop=loop)
367
- else:
368
- app_queue = asyncio.Queue()
369
- app_queue.put_nowait({'type': 'http.request', 'body': body, 'more_body': False})
370
-
371
- # Prepare ASGI receive function
372
- async def receive():
373
- message = await app_queue.get()
374
- return message
375
-
376
- # Prepare ASGI send function
377
- response_started = False
378
- async def send(event):
379
- nonlocal response_started
380
- if event['type'] == 'http.response.start':
381
- self.send_response(event['status'])
382
- if 'headers' in event:
383
- for name, value in event['headers']:
384
- self.send_header(name.decode(), value.decode())
385
- self.end_headers()
386
- response_started = True
387
- elif event['type'] == 'http.response.body':
388
- self.wfile.write(event['body'])
389
- if not event.get('more_body', False):
390
- self.wfile.flush()
583
+ # Mark IPC as ready and flush any buffered init logs
584
+ _ipc_ready = True
585
+ for m in _init_log_buf:
586
+ send_message(m)
587
+ _init_log_buf.clear()
391
588
 
392
- # Run the ASGI application
393
- asgi_instance = app(scope, receive, send)
394
- if _use_legacy_asyncio:
395
- asgi_task = loop.create_task(asgi_instance)
396
- loop.run_until_complete(asgi_task)
397
- else:
398
- asyncio.run(asgi_instance)
589
+ # Run the server (blocking)
590
+ server.run()
591
+ # If the server ever returns, exit
592
+ sys.exit(0)
399
593
 
400
594
  if 'Handler' in locals():
401
595
  server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
@@ -406,10 +600,15 @@ if 'VERCEL_IPC_PATH' in os.environ:
406
600
  "httpPort": server.server_address[1],
407
601
  }
408
602
  })
603
+ # Mark IPC as ready and flush any buffered init logs
604
+ _ipc_ready = True
605
+ for m in _init_log_buf:
606
+ send_message(m)
607
+ _init_log_buf.clear()
409
608
  server.serve_forever()
410
609
 
411
- print('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
412
- print('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
610
+ _stderr('Missing variable `handler` or `app` in file "__VC_HANDLER_ENTRYPOINT".')
611
+ _stderr('See the docs: https://vercel.com/docs/functions/serverless-functions/runtimes/python')
413
612
  exit(1)
414
613
 
415
614
  if 'handler' in __vc_variables or 'Handler' in __vc_variables:
@@ -626,7 +825,18 @@ elif 'app' in __vc_variables:
626
825
  )
627
826
 
628
827
  status_code = message['status']
629
- headers = Headers(message.get('headers', []))
828
+ raw_headers = message.get('headers', [])
829
+
830
+ # Headers from werkzeug transform bytes header value
831
+ # from b'value' to "b'value'" so we need to process
832
+ # ASGI headers manually
833
+ decoded_headers = []
834
+ for key, value in raw_headers:
835
+ decoded_key = key.decode() if isinstance(key, bytes) else key
836
+ decoded_value = value.decode() if isinstance(value, bytes) else value
837
+ decoded_headers.append((decoded_key, decoded_value))
838
+
839
+ headers = Headers(decoded_headers)
630
840
 
631
841
  self.on_request(headers, status_code)
632
842
  self.state = ASGICycleState.RESPONSE
@@ -39,8 +39,9 @@ if _app is None:
39
39
  f"Missing 'app' in module '{USER_MODULE}'. Define `app = ...` (ASGI app)."
40
40
  )
41
41
 
42
- # Sanic compatibility: prefer `app.asgi` when available
43
- USER_ASGI_APP = getattr(_app, 'asgi', _app)
42
+ # Prefer a callable app.asgi when available; some frameworks expose a boolean here
43
+ _CAND = getattr(_app, 'asgi', None)
44
+ USER_ASGI_APP = _CAND if callable(_CAND) else _app
44
45
 
45
46
  PUBLIC_DIR = 'public'
46
47
 
@@ -4,9 +4,9 @@
4
4
  "vercel": "./dist/vc.js"
5
5
  },
6
6
  "dependencies": {
7
- "@vercel/build-utils": "12.2.1",
7
+ "@vercel/build-utils": "13.2.12",
8
8
  "@vercel/detect-agent": "1.0.0",
9
- "@vercel/python": "5.0.10"
9
+ "@vercel/python": "6.1.6"
10
10
  },
11
11
  "description": "The command-line interface for Vercel",
12
12
  "engines": {
@@ -30,12 +30,13 @@
30
30
  "dev": "echo \"'pnpm dev [command]' has been removed. Use 'pnpm vercel [command]' instead.\" && exit 1",
31
31
  "test": "jest --reporters=default --reporters=jest-junit --env node --verbose --bail",
32
32
  "test-dev": "pnpm test test/dev/",
33
- "test-e2e": "rimraf test/fixtures/integration && pnpm test test/integration-1.test.ts test/integration-2.test.ts test/integration-3.test.ts",
33
+ "test-e2e-node-all-versions": "rimraf test/fixtures/integration && pnpm test test/integration-1.test.ts test/integration-2.test.ts test/integration-3.test.ts test/integration-link-env-pull.test.ts",
34
34
  "type-check": "tsc --noEmit",
35
35
  "vc": "pnpm vercel",
36
36
  "vercel": "pnpm build && node ./dist/vc.js",
37
37
  "vitest-run": "vitest --config ./vitest.config.mts",
38
38
  "vitest-unit": "jest test/unit/ --listTests"
39
39
  },
40
- "version": "48.6.6"
40
+ "type": "module",
41
+ "version": "50.4.6"
41
42
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vercel-cli
3
- Version: 48.6.6
3
+ Version: 50.4.6
4
4
  Summary: Vercel CLI packaged for Python (bundled Node.js, vendored npm)
5
5
  Project-URL: Homepage, https://github.com/nuage-studio/vercel-cli-python
6
6
  Project-URL: Repository, https://github.com/nuage-studio/vercel-cli-python