@emulators/vercel 0.4.1 → 0.6.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.
Binary file
package/dist/index.js CHANGED
@@ -6,7 +6,10 @@ function getVercelStore(store) {
6
6
  teamMembers: store.collection("vercel.team_members", ["teamId", "userId"]),
7
7
  projects: store.collection("vercel.projects", ["uid", "name", "accountId"]),
8
8
  deployments: store.collection("vercel.deployments", ["uid", "projectId", "url"]),
9
- deploymentAliases: store.collection("vercel.deployment_aliases", ["deploymentId", "projectId"]),
9
+ deploymentAliases: store.collection("vercel.deployment_aliases", [
10
+ "deploymentId",
11
+ "projectId"
12
+ ]),
10
13
  builds: store.collection("vercel.builds", ["deploymentId"]),
11
14
  deploymentEvents: store.collection("vercel.deployment_events", ["deploymentId"]),
12
15
  files: store.collection("vercel.files", ["digest"]),
@@ -51,7 +54,7 @@ function resolveTeamScope(c, vs) {
51
54
  return { accountId: user.uid, team: null };
52
55
  }
53
56
  function lookupProject(vs, idOrName, accountId) {
54
- let project = vs.projects.findOneBy("uid", idOrName);
57
+ const project = vs.projects.findOneBy("uid", idOrName);
55
58
  if (project && project.accountId === accountId) return project;
56
59
  const byName = vs.projects.findBy("name", idOrName);
57
60
  return byName.find((p) => p.accountId === accountId);
@@ -242,8 +245,6 @@ function formatEnvVar(env, decrypt = false) {
242
245
  }
243
246
 
244
247
  // ../core/dist/index.js
245
- import { Hono } from "hono";
246
- import { cors } from "hono/cors";
247
248
  import { readFileSync } from "fs";
248
249
  import { fileURLToPath } from "url";
249
250
  import { dirname, join } from "path";
@@ -287,6 +288,7 @@ var FONTS = {
287
288
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
288
289
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
289
290
  };
291
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
290
292
  function escapeHtml(s) {
291
293
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
292
294
  }
@@ -438,6 +440,132 @@ body{
438
440
  .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
439
441
  .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
440
442
  .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
443
+
444
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
445
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
446
+ .inspector-tabs a{
447
+ padding:7px 16px;border-radius:6px;text-decoration:none;
448
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
449
+ transition:color .15s,border-color .15s;
450
+ }
451
+ .inspector-tabs a:hover{color:#33ff00;}
452
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
453
+ .inspector-section{margin-bottom:24px;}
454
+ .inspector-section h2{
455
+ font-family:'Geist Pixel',monospace;
456
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
457
+ }
458
+ .inspector-section h3{
459
+ font-family:'Geist Pixel',monospace;
460
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
461
+ }
462
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
463
+ .inspector-table th,.inspector-table td{
464
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
465
+ font-size:.8125rem;
466
+ }
467
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
468
+ .inspector-table td{color:#33ff00;}
469
+ .inspector-table tbody tr{transition:background .1s;}
470
+ .inspector-table tbody tr:hover{background:#0a3300;}
471
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
472
+
473
+ .checkout-layout{
474
+ display:flex;min-height:calc(100vh - 42px);
475
+ }
476
+ .checkout-summary{
477
+ flex:1;background:#020;padding:48px 40px 48px 10%;
478
+ display:flex;flex-direction:column;justify-content:center;
479
+ border-right:1px solid #0a3300;
480
+ }
481
+ .checkout-form-side{
482
+ flex:1;background:#000;padding:48px 10% 48px 40px;
483
+ display:flex;flex-direction:column;justify-content:center;
484
+ }
485
+ .checkout-merchant{
486
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
487
+ }
488
+ .checkout-merchant-name{
489
+ font-family:'Geist Pixel',monospace;
490
+ font-size:.9375rem;font-weight:600;color:#33ff00;
491
+ }
492
+ .checkout-test-badge{
493
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
494
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
495
+ }
496
+ .checkout-total{
497
+ font-family:'Geist Pixel',monospace;
498
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
499
+ }
500
+ .checkout-line-item{
501
+ display:flex;align-items:center;gap:14px;padding:14px 0;
502
+ border-bottom:1px solid #0a3300;
503
+ }
504
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
505
+ .checkout-item-icon{
506
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
507
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
508
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
509
+ }
510
+ .checkout-item-details{flex:1;min-width:0;}
511
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
512
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
513
+ .checkout-item-price{
514
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
515
+ }
516
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
517
+ .checkout-totals{margin-top:20px;}
518
+ .checkout-totals-row{
519
+ display:flex;justify-content:space-between;padding:6px 0;
520
+ font-size:.8125rem;color:#1a8c00;
521
+ }
522
+ .checkout-totals-row.total{
523
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
524
+ font-size:.9375rem;font-weight:600;color:#33ff00;
525
+ }
526
+ .checkout-form-section{margin-bottom:24px;}
527
+ .checkout-form-label{
528
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
529
+ }
530
+ .checkout-input{
531
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
532
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
533
+ transition:border-color .15s;outline:none;
534
+ }
535
+ .checkout-input:focus{border-color:#33ff00;}
536
+ .checkout-input::placeholder{color:#116600;}
537
+ .checkout-card-box{
538
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
539
+ background:#020;
540
+ }
541
+ .checkout-card-row{
542
+ display:flex;gap:12px;margin-top:10px;
543
+ }
544
+ .checkout-card-row .checkout-input{flex:1;}
545
+ .checkout-sim-note{
546
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
547
+ font-style:italic;
548
+ }
549
+ .checkout-pay-btn{
550
+ width:100%;padding:14px;border:none;border-radius:8px;
551
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
552
+ cursor:pointer;transition:background .15s;
553
+ font-family:'Geist Pixel',monospace;
554
+ }
555
+ .checkout-pay-btn:hover{background:#44ff22;}
556
+ .checkout-cancel{
557
+ text-align:center;margin-top:14px;
558
+ }
559
+ .checkout-cancel a{
560
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
561
+ transition:color .15s;
562
+ }
563
+ .checkout-cancel a:hover{color:#33ff00;}
564
+ @media(max-width:768px){
565
+ .checkout-layout{flex-direction:column;}
566
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
567
+ .checkout-form-side{padding:32px 20px;}
568
+ }
441
569
  `;
442
570
  var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
443
571
  function emuBar(service) {
@@ -457,6 +585,7 @@ function head(title) {
457
585
  <head>
458
586
  <meta charset="utf-8"/>
459
587
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
588
+ <link rel="icon" href="/_emulate/favicon.ico"/>
460
589
  <title>${escapeHtml(title)} | emulate</title>
461
590
  <style>${CSS}</style>
462
591
  </head>`;
@@ -938,7 +1067,9 @@ function projectsRoutes({ app, store, baseUrl }) {
938
1067
  key,
939
1068
  value: typeof ev.value === "string" ? ev.value : String(ev.value ?? ""),
940
1069
  type: ev.type === "system" || ev.type === "encrypted" || ev.type === "plain" || ev.type === "secret" || ev.type === "sensitive" ? ev.type : "encrypted",
941
- target: Array.isArray(ev.target) ? ev.target.filter((t) => t === "production" || t === "preview" || t === "development") : ["production", "preview", "development"],
1070
+ target: Array.isArray(ev.target) ? ev.target.filter(
1071
+ (t) => t === "production" || t === "preview" || t === "development"
1072
+ ) : ["production", "preview", "development"],
942
1073
  gitBranch: typeof ev.gitBranch === "string" ? ev.gitBranch : null,
943
1074
  customEnvironmentIds: Array.isArray(ev.customEnvironmentIds) ? ev.customEnvironmentIds : [],
944
1075
  comment: typeof ev.comment === "string" ? ev.comment : null,
@@ -1927,11 +2058,17 @@ function parseEnvRow(body) {
1927
2058
  }
1928
2059
  const type = parseType(body.type);
1929
2060
  if (type === "invalid") {
1930
- return { row: {}, error: "Invalid value: type must be one of system, encrypted, plain, secret, sensitive" };
2061
+ return {
2062
+ row: {},
2063
+ error: "Invalid value: type must be one of system, encrypted, plain, secret, sensitive"
2064
+ };
1931
2065
  }
1932
2066
  const target = parseTarget(body.target);
1933
2067
  if (target === "invalid") {
1934
- return { row: {}, error: "Invalid value: target must be a non-empty array of production, preview, development" };
2068
+ return {
2069
+ row: {},
2070
+ error: "Invalid value: target must be a non-empty array of production, preview, development"
2071
+ };
1935
2072
  }
1936
2073
  const customEnvironmentIds = parseCustomEnvironmentIds(body.customEnvironmentIds);
1937
2074
  if (customEnvironmentIds === "invalid") {
@@ -2039,9 +2176,7 @@ function envRoutes({ app, store }) {
2039
2176
  }
2040
2177
  const { row } = parsed;
2041
2178
  const existingDb = findEnvByKeyAndTargetsOverlap(vs, project.uid, row.key, row.target);
2042
- const existingPending = pending.find(
2043
- (e) => e.key === row.key && targetsOverlap(e.target, row.target)
2044
- );
2179
+ const existingPending = pending.find((e) => e.key === row.key && targetsOverlap(e.target, row.target));
2045
2180
  if (upsert) {
2046
2181
  const toUpdate = existingDb ?? existingPending;
2047
2182
  if (toUpdate) {
@@ -2140,14 +2275,24 @@ function envRoutes({ app, store }) {
2140
2275
  if ("type" in body) {
2141
2276
  const t = parseType(body.type);
2142
2277
  if (t === "invalid") {
2143
- return vercelErr5(c, 400, "bad_request", "Invalid value: type must be one of system, encrypted, plain, secret, sensitive");
2278
+ return vercelErr5(
2279
+ c,
2280
+ 400,
2281
+ "bad_request",
2282
+ "Invalid value: type must be one of system, encrypted, plain, secret, sensitive"
2283
+ );
2144
2284
  }
2145
2285
  patch.type = t;
2146
2286
  }
2147
2287
  if ("target" in body) {
2148
2288
  const t = parseTarget(body.target);
2149
2289
  if (t === "invalid") {
2150
- return vercelErr5(c, 400, "bad_request", "Invalid value: target must be a non-empty array of production, preview, development");
2290
+ return vercelErr5(
2291
+ c,
2292
+ 400,
2293
+ "bad_request",
2294
+ "Invalid value: target must be a non-empty array of production, preview, development"
2295
+ );
2151
2296
  }
2152
2297
  patch.target = t;
2153
2298
  }
@@ -2245,11 +2390,23 @@ function oauthRoutes({ app, store, tokenMap }) {
2245
2390
  if (integrationsConfigured) {
2246
2391
  const integration = vs.integrations.findOneBy("client_id", client_id);
2247
2392
  if (!integration) {
2248
- return c.html(renderErrorPage("Application not found", `The client_id '${client_id}' is not registered.`, SERVICE_LABEL), 400);
2393
+ return c.html(
2394
+ renderErrorPage("Application not found", `The client_id '${client_id}' is not registered.`, SERVICE_LABEL),
2395
+ 400
2396
+ );
2249
2397
  }
2250
2398
  if (redirect_uri && !matchesRedirectUri(redirect_uri, integration.redirect_uris)) {
2251
- console.warn(`[OAuth] redirect_uri mismatch: got "${redirect_uri}", registered: ${JSON.stringify(integration.redirect_uris)}`);
2252
- return c.html(renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL), 400);
2399
+ console.warn(
2400
+ `[OAuth] redirect_uri mismatch: got "${redirect_uri}", registered: ${JSON.stringify(integration.redirect_uris)}`
2401
+ );
2402
+ return c.html(
2403
+ renderErrorPage(
2404
+ "Redirect URI mismatch",
2405
+ "The redirect_uri is not registered for this application.",
2406
+ SERVICE_LABEL
2407
+ ),
2408
+ 400
2409
+ );
2253
2410
  }
2254
2411
  integrationName = integration.name;
2255
2412
  }
@@ -2297,7 +2454,10 @@ function oauthRoutes({ app, store, tokenMap }) {
2297
2454
  codeChallengeMethod: code_challenge_method || null,
2298
2455
  created_at: Date.now()
2299
2456
  });
2300
- debug("vercel.oauth", `[Vercel callback] generated code: ${code.slice(0, 8)}... for username=${username}, challenge=${code_challenge ? "present" : "none"}, pendingCodes size: ${pendingCodes.size}`);
2457
+ debug(
2458
+ "vercel.oauth",
2459
+ `[Vercel callback] generated code: ${code.slice(0, 8)}... for username=${username}, challenge=${code_challenge ? "present" : "none"}, pendingCodes size: ${pendingCodes.size}`
2460
+ );
2301
2461
  const url = new URL(redirect_uri);
2302
2462
  url.searchParams.set("code", code);
2303
2463
  if (state !== "") url.searchParams.set("state", state);
@@ -2309,7 +2469,10 @@ function oauthRoutes({ app, store, tokenMap }) {
2309
2469
  const pendingCodes = getPendingCodes(store);
2310
2470
  debug("vercel.oauth", `[Vercel token] Content-Type: ${contentType}`);
2311
2471
  debug("vercel.oauth", `[Vercel token] pendingCodes size: ${pendingCodes.size}`);
2312
- debug("vercel.oauth", `[Vercel token] pendingCodes keys: ${[...pendingCodes.keys()].map((k) => k.slice(0, 8) + "...").join(", ")}`);
2472
+ debug(
2473
+ "vercel.oauth",
2474
+ `[Vercel token] pendingCodes keys: ${[...pendingCodes.keys()].map((k) => k.slice(0, 8) + "...").join(", ")}`
2475
+ );
2313
2476
  const rawText = await c.req.text();
2314
2477
  debug("vercel.oauth", `[Vercel token] raw body: ${rawText.slice(0, 500)}`);
2315
2478
  let body;
@@ -2331,73 +2494,70 @@ function oauthRoutes({ app, store, tokenMap }) {
2331
2494
  debug("vercel.oauth", `[Vercel token] code: ${code.slice(0, 8)}... (len=${code.length})`);
2332
2495
  debug("vercel.oauth", `[Vercel token] client_id: ${bodyClientId}`);
2333
2496
  debug("vercel.oauth", `[Vercel token] client_secret: ${bodyClientSecret.slice(0, 4)}****`);
2334
- debug("vercel.oauth", `[Vercel token] code_verifier: ${code_verifier ? code_verifier.slice(0, 8) + "..." : "undefined"}`);
2497
+ debug(
2498
+ "vercel.oauth",
2499
+ `[Vercel token] code_verifier: ${code_verifier ? code_verifier.slice(0, 8) + "..." : "undefined"}`
2500
+ );
2335
2501
  const integrationsConfigured = vs.integrations.all().length > 0;
2336
2502
  if (integrationsConfigured) {
2337
2503
  const integration = vs.integrations.findOneBy("client_id", bodyClientId);
2338
2504
  if (!integration) {
2339
2505
  debug("vercel.oauth", `[Vercel token] REJECTED: client_id not found`);
2340
- return c.json({ error: "invalid_client", error_description: "The client_id and/or client_secret passed are incorrect." }, 401);
2506
+ return c.json(
2507
+ { error: "invalid_client", error_description: "The client_id and/or client_secret passed are incorrect." },
2508
+ 401
2509
+ );
2341
2510
  }
2342
2511
  if (!constantTimeSecretEqual(bodyClientSecret, integration.client_secret)) {
2343
2512
  debug("vercel.oauth", `[Vercel token] REJECTED: client_secret mismatch`);
2344
- return c.json({ error: "invalid_client", error_description: "The client_id and/or client_secret passed are incorrect." }, 401);
2513
+ return c.json(
2514
+ { error: "invalid_client", error_description: "The client_id and/or client_secret passed are incorrect." },
2515
+ 401
2516
+ );
2345
2517
  }
2346
2518
  debug("vercel.oauth", `[Vercel token] client credentials OK (${integration.name})`);
2347
2519
  }
2348
2520
  const pending = pendingCodes.get(code);
2349
2521
  if (!pending) {
2350
2522
  debug("vercel.oauth", `[Vercel token] REJECTED: code not found in pendingCodes`);
2351
- return c.json(
2352
- { error: "invalid_grant", error_description: "The code passed is incorrect or expired." },
2353
- 400
2354
- );
2523
+ return c.json({ error: "invalid_grant", error_description: "The code passed is incorrect or expired." }, 400);
2355
2524
  }
2356
2525
  if (isPendingCodeExpired(pending)) {
2357
2526
  debug("vercel.oauth", `[Vercel token] REJECTED: code expired`);
2358
2527
  pendingCodes.delete(code);
2359
- return c.json(
2360
- { error: "invalid_grant", error_description: "The code passed is incorrect or expired." },
2361
- 400
2362
- );
2528
+ return c.json({ error: "invalid_grant", error_description: "The code passed is incorrect or expired." }, 400);
2363
2529
  }
2364
2530
  debug("vercel.oauth", `[Vercel token] code valid, username=${pending.username}, scope=${pending.scope}`);
2365
2531
  if (redirect_uri && pending.redirectUri && redirect_uri !== pending.redirectUri) {
2366
- debug("vercel.oauth", `[Vercel token] REJECTED: redirect_uri mismatch (got "${redirect_uri}", expected "${pending.redirectUri}")`);
2532
+ debug(
2533
+ "vercel.oauth",
2534
+ `[Vercel token] REJECTED: redirect_uri mismatch (got "${redirect_uri}", expected "${pending.redirectUri}")`
2535
+ );
2367
2536
  pendingCodes.delete(code);
2368
2537
  return c.json(
2369
- { error: "invalid_grant", error_description: "The redirect_uri does not match the one used during authorization." },
2538
+ {
2539
+ error: "invalid_grant",
2540
+ error_description: "The redirect_uri does not match the one used during authorization."
2541
+ },
2370
2542
  400
2371
2543
  );
2372
2544
  }
2373
2545
  if (pending.codeChallenge != null) {
2374
2546
  if (code_verifier === void 0) {
2375
- return c.json(
2376
- { error: "invalid_grant", error_description: "PKCE verification failed." },
2377
- 400
2378
- );
2547
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
2379
2548
  }
2380
2549
  const method = (pending.codeChallengeMethod ?? "plain").toLowerCase();
2381
2550
  if (method === "s256") {
2382
2551
  const expected = createHash("sha256").update(code_verifier).digest("base64url");
2383
2552
  if (expected !== pending.codeChallenge) {
2384
- return c.json(
2385
- { error: "invalid_grant", error_description: "PKCE verification failed." },
2386
- 400
2387
- );
2553
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
2388
2554
  }
2389
2555
  } else if (method === "plain") {
2390
2556
  if (code_verifier !== pending.codeChallenge) {
2391
- return c.json(
2392
- { error: "invalid_grant", error_description: "PKCE verification failed." },
2393
- 400
2394
- );
2557
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
2395
2558
  }
2396
2559
  } else {
2397
- return c.json(
2398
- { error: "invalid_grant", error_description: "PKCE verification failed." },
2399
- 400
2400
- );
2560
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
2401
2561
  }
2402
2562
  }
2403
2563
  debug("vercel.oauth", `[Vercel token] PKCE OK (challenge=${pending.codeChallenge ? "present" : "none"})`);
@@ -2415,7 +2575,10 @@ function oauthRoutes({ app, store, tokenMap }) {
2415
2575
  if (tokenMap) {
2416
2576
  tokenMap.set(token, { login: user.username, id: user.id, scopes });
2417
2577
  }
2418
- debug("vercel.oauth", `[Vercel token] SUCCESS: issued token for ${user.username} (scopes: ${scopes.join(",") || "none"})`);
2578
+ debug(
2579
+ "vercel.oauth",
2580
+ `[Vercel token] SUCCESS: issued token for ${user.username} (scopes: ${scopes.join(",") || "none"})`
2581
+ );
2419
2582
  return c.json({
2420
2583
  access_token: token,
2421
2584
  token_type: "Bearer",