emulate 0.1.1 → 0.2.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
@@ -3621,6 +3621,9 @@ var SignJWT = class {
3621
3621
  };
3622
3622
 
3623
3623
  // ../@internal/core/dist/index.js
3624
+ import { readFileSync } from "fs";
3625
+ import { fileURLToPath } from "url";
3626
+ import { dirname, join } from "path";
3624
3627
  import { timingSafeEqual } from "crypto";
3625
3628
  var Collection = class {
3626
3629
  constructor(indexFields = []) {
@@ -3994,6 +3997,25 @@ function authMiddleware(tokens, appKeyResolver, fallbackUser) {
3994
3997
  await next();
3995
3998
  };
3996
3999
  }
4000
+ var __dirname = dirname(fileURLToPath(import.meta.url));
4001
+ var FONTS = {
4002
+ "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
4003
+ "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
4004
+ };
4005
+ function registerFontRoutes(app) {
4006
+ app.get("/_emulate/fonts/:name", (c) => {
4007
+ const name = c.req.param("name");
4008
+ const buf = FONTS[name];
4009
+ if (!buf) return c.notFound();
4010
+ return new Response(buf, {
4011
+ headers: {
4012
+ "Content-Type": "font/woff2",
4013
+ "Cache-Control": "public, max-age=31536000, immutable",
4014
+ "Access-Control-Allow-Origin": "*"
4015
+ }
4016
+ });
4017
+ });
4018
+ }
3997
4019
  function createServer(plugin, options = {}) {
3998
4020
  const port = options.port ?? 4e3;
3999
4021
  const baseUrl = options.baseUrl ?? `http://localhost:${port}`;
@@ -4011,6 +4033,7 @@ function createServer(plugin, options = {}) {
4011
4033
  }
4012
4034
  }
4013
4035
  const docsUrl = options.docsUrl ?? `https://emulate.dev/${plugin.name}`;
4036
+ registerFontRoutes(app);
4014
4037
  app.onError(createApiErrorHandler(docsUrl));
4015
4038
  app.use("*", cors());
4016
4039
  app.use("*", createErrorHandler(docsUrl));
@@ -4092,137 +4115,161 @@ function escapeAttr(s) {
4092
4115
  return escapeHtml(s).replace(/'/g, "'");
4093
4116
  }
4094
4117
  var CSS = `
4118
+ @font-face{
4119
+ font-family:'Geist';font-style:normal;font-weight:100 900;font-display:swap;
4120
+ src:url('/_emulate/fonts/geist-sans.woff2') format('woff2');
4121
+ }
4122
+ @font-face{
4123
+ font-family:'Geist Pixel';font-style:normal;font-weight:400;font-display:swap;
4124
+ src:url('/_emulate/fonts/GeistPixel-Square.woff2') format('woff2');
4125
+ }
4095
4126
  *{box-sizing:border-box;margin:0;padding:0}
4096
4127
  body{
4097
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
4098
- background:#0a0a0a;color:#e4e4e7;min-height:100vh;
4128
+ font-family:'Geist',-apple-system,BlinkMacSystemFont,sans-serif;
4129
+ background:#000;color:#33ff00;min-height:100vh;
4130
+ -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
4099
4131
  }
4100
4132
  .emu-bar{
4101
- background:#141414;border-bottom:1px solid #2a2a2a;padding:10px 20px;
4102
- display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#a1a1aa;
4133
+ border-bottom:1px solid #0a3300;padding:10px 20px;
4134
+ display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#1a8c00;
4135
+ }
4136
+ .emu-bar-title{font-weight:600;color:#33ff00;font-family:'Geist Pixel',monospace;}
4137
+ .emu-bar-links{margin-left:auto;display:flex;gap:16px;}
4138
+ .emu-bar-links a{
4139
+ color:#1a8c00;font-size:.75rem;text-decoration:none;transition:color .15s;
4103
4140
  }
4104
- .emu-badge{
4105
- display:inline-flex;align-items:center;gap:4px;
4106
- background:#422006;color:#fbbf24;font-size:.6875rem;font-weight:700;
4107
- padding:2px 8px;border-radius:4px;letter-spacing:.04em;text-transform:uppercase;
4141
+ .emu-bar-links a:hover{color:#33ff00;}
4142
+ .emu-bar-links a .full{display:inline;}
4143
+ .emu-bar-links a .short{display:none;}
4144
+ @media(max-width:600px){
4145
+ .emu-bar-links a .full{display:none;}
4146
+ .emu-bar-links a .short{display:inline;}
4108
4147
  }
4109
- .emu-bar-title{font-weight:600;color:#e4e4e7;}
4110
- .emu-bar-service{color:#71717a;margin-left:auto;font-size:.75rem;}
4111
4148
 
4112
- /* Card page (consent, error) */
4113
- .card-wrap{
4149
+ .content{
4114
4150
  display:flex;align-items:center;justify-content:center;
4115
4151
  min-height:calc(100vh - 42px);padding:24px 16px;
4116
4152
  }
4117
- .card{
4118
- background:#141414;border:1px solid #2a2a2a;border-radius:10px;
4119
- padding:24px 22px 18px;width:100%;max-width:420px;
4120
- box-shadow:0 12px 40px rgba(0,0,0,.5);
4153
+ .content-inner{width:100%;max-width:420px;}
4154
+ .card-title{
4155
+ font-family:'Geist Pixel',monospace;
4156
+ font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#33ff00;
4121
4157
  }
4122
- .card-title{font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#e4e4e7;}
4123
- .card-subtitle{color:#71717a;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
4124
- .card-footer{
4125
- margin-top:18px;padding-top:14px;border-top:1px solid #1e1e1e;
4126
- text-align:center;font-size:.6875rem;color:#52525b;
4158
+ .card-subtitle{color:#1a8c00;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
4159
+ .powered-by{
4160
+ position:fixed;bottom:0;left:0;right:0;
4161
+ text-align:center;padding:12px;font-size:.6875rem;color:#0a3300;
4162
+ font-family:'Geist Pixel',monospace;
4127
4163
  }
4164
+ .powered-by a{color:#1a8c00;text-decoration:none;transition:color .15s;}
4165
+ .powered-by a:hover{color:#33ff00;}
4128
4166
 
4129
- /* Error card */
4130
- .error-title{color:#ef4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;}
4131
- .error-msg{color:#a1a1aa;font-size:.875rem;line-height:1.5;}
4167
+ .error-title{
4168
+ font-family:'Geist Pixel',monospace;
4169
+ color:#ff4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;
4170
+ }
4171
+ .error-msg{color:#1a8c00;font-size:.875rem;line-height:1.5;}
4132
4172
  .error-card{text-align:center;}
4133
4173
 
4134
- /* User button */
4135
4174
  .user-form{margin-bottom:8px;}
4136
4175
  .user-form:last-of-type{margin-bottom:0;}
4137
4176
  .user-btn{
4138
4177
  width:100%;display:flex;align-items:center;gap:12px;
4139
- padding:10px 12px;border:1px solid #2a2a2a;border-radius:8px;
4140
- background:#0a0a0a;color:inherit;cursor:pointer;text-align:left;
4141
- font:inherit;transition:background .15s,border-color .15s;
4178
+ padding:10px 12px;border:1px solid #0a3300;border-radius:8px;
4179
+ background:#000;color:inherit;cursor:pointer;text-align:left;
4180
+ font:inherit;transition:border-color .15s;
4142
4181
  }
4143
- .user-btn:hover{background:#1a1a1a;border-color:#fbbf24;}
4182
+ .user-btn:hover{border-color:#33ff00;}
4144
4183
  .avatar{
4145
4184
  width:36px;height:36px;border-radius:50%;
4146
- background:#422006;color:#fbbf24;font-weight:600;font-size:.875rem;
4185
+ background:#0a3300;color:#33ff00;font-weight:600;font-size:.875rem;
4147
4186
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
4187
+ font-family:'Geist Pixel',monospace;
4148
4188
  }
4149
4189
  .user-text{min-width:0;}
4150
- .user-login{font-weight:600;font-size:.875rem;display:block;color:#e4e4e7;}
4151
- .user-meta{color:#71717a;font-size:.75rem;margin-top:1px;}
4152
- .user-email{font-size:.6875rem;color:#52525b;word-break:break-all;margin-top:1px;}
4190
+ .user-login{font-weight:600;font-size:.875rem;display:block;color:#33ff00;}
4191
+ .user-meta{color:#1a8c00;font-size:.75rem;margin-top:1px;}
4192
+ .user-email{font-size:.6875rem;color:#116600;word-break:break-all;margin-top:1px;}
4153
4193
 
4154
- /* Settings layout */
4155
4194
  .settings-layout{
4156
4195
  max-width:920px;margin:0 auto;padding:28px 20px;
4157
4196
  display:flex;gap:28px;
4158
4197
  }
4159
4198
  .settings-sidebar{width:200px;flex-shrink:0;}
4160
4199
  .settings-sidebar a{
4161
- display:block;padding:6px 10px;border-radius:6px;color:#a1a1aa;
4162
- text-decoration:none;font-size:.8125rem;transition:background .15s;
4200
+ display:block;padding:6px 10px;border-radius:6px;color:#1a8c00;
4201
+ text-decoration:none;font-size:.8125rem;transition:color .15s;
4163
4202
  }
4164
- .settings-sidebar a:hover{background:#1a1a1a;color:#e4e4e7;}
4165
- .settings-sidebar a.active{background:#1a1a1a;color:#e4e4e7;font-weight:600;}
4203
+ .settings-sidebar a:hover{color:#33ff00;}
4204
+ .settings-sidebar a.active{color:#33ff00;font-weight:600;}
4166
4205
  .settings-main{flex:1;min-width:0;}
4167
4206
 
4168
- /* Settings cards */
4169
4207
  .s-card{
4170
- background:#141414;border:1px solid #2a2a2a;border-radius:8px;
4171
- padding:18px;margin-bottom:14px;
4208
+ padding:18px 0;margin-bottom:14px;border-bottom:1px solid #0a3300;
4172
4209
  }
4210
+ .s-card:last-child{border-bottom:none;}
4173
4211
  .s-card-header{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
4174
4212
  .s-icon{
4175
4213
  width:42px;height:42px;border-radius:8px;
4176
- background:#1e1e1e;display:flex;align-items:center;justify-content:center;
4177
- font-size:1.125rem;font-weight:700;color:#71717a;flex-shrink:0;
4214
+ background:#0a3300;display:flex;align-items:center;justify-content:center;
4215
+ font-size:1.125rem;font-weight:700;color:#116600;flex-shrink:0;
4216
+ font-family:'Geist Pixel',monospace;
4178
4217
  }
4179
- .s-title{font-size:1.25rem;font-weight:600;color:#e4e4e7;}
4180
- .s-subtitle{font-size:.75rem;color:#71717a;margin-top:2px;}
4218
+ .s-title{
4219
+ font-family:'Geist Pixel',monospace;
4220
+ font-size:1.25rem;font-weight:600;color:#33ff00;
4221
+ }
4222
+ .s-subtitle{font-size:.75rem;color:#1a8c00;margin-top:2px;}
4181
4223
  .section-heading{
4182
- font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#e4e4e7;
4224
+ font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#33ff00;
4183
4225
  display:flex;align-items:center;justify-content:space-between;
4184
4226
  }
4185
4227
  .perm-list{list-style:none;}
4186
- .perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#a1a1aa;}
4187
- .check{color:#22c55e;}
4228
+ .perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#1a8c00;}
4229
+ .check{color:#33ff00;}
4188
4230
  .org-row{
4189
4231
  display:flex;align-items:center;gap:8px;padding:7px 0;
4190
- border-bottom:1px solid #1e1e1e;font-size:.8125rem;
4232
+ border-bottom:1px solid #0a3300;font-size:.8125rem;
4191
4233
  }
4192
4234
  .org-row:last-child{border-bottom:none;}
4193
4235
  .org-icon{
4194
- width:22px;height:22px;border-radius:4px;background:#1e1e1e;
4236
+ width:22px;height:22px;border-radius:4px;background:#0a3300;
4195
4237
  display:flex;align-items:center;justify-content:center;
4196
- font-size:.625rem;font-weight:700;color:#71717a;flex-shrink:0;
4238
+ font-size:.625rem;font-weight:700;color:#116600;flex-shrink:0;
4239
+ font-family:'Geist Pixel',monospace;
4197
4240
  }
4198
- .org-name{font-weight:600;color:#e4e4e7;}
4241
+ .org-name{font-weight:600;color:#33ff00;}
4199
4242
  .badge{font-size:.6875rem;padding:1px 7px;border-radius:999px;font-weight:500;}
4200
- .badge-granted{background:#052e16;color:#22c55e;}
4201
- .badge-denied{background:#2a0a0a;color:#ef4444;}
4202
- .badge-requested{background:#2a1a05;color:#f59e0b;}
4243
+ .badge-granted{background:#0a3300;color:#33ff00;}
4244
+ .badge-denied{background:#1a0a0a;color:#ff4444;}
4245
+ .badge-requested{background:#0a3300;color:#1a8c00;}
4203
4246
  .btn-revoke{
4204
4247
  display:inline-block;padding:5px 14px;border-radius:6px;
4205
- border:1px solid #ef4444;background:transparent;color:#ef4444;
4206
- font-size:.75rem;font-weight:600;cursor:pointer;transition:background .15s;
4248
+ border:1px solid #0a3300;background:transparent;color:#ff4444;
4249
+ font-size:.75rem;font-weight:600;cursor:pointer;transition:border-color .15s;
4207
4250
  }
4208
- .btn-revoke:hover{background:rgba(239,68,68,.1);}
4209
- .info-text{color:#71717a;font-size:.75rem;line-height:1.5;margin-top:10px;}
4251
+ .btn-revoke:hover{border-color:#ff4444;}
4252
+ .info-text{color:#1a8c00;font-size:.75rem;line-height:1.5;margin-top:10px;}
4210
4253
  .app-link{
4211
4254
  display:flex;align-items:center;gap:12px;padding:12px;
4212
- border:1px solid #2a2a2a;border-radius:8px;background:#0a0a0a;
4213
- text-decoration:none;color:inherit;margin-bottom:8px;transition:background .15s,border-color .15s;
4214
- }
4215
- .app-link:hover{background:#1a1a1a;border-color:#fbbf24;}
4216
- .app-link-name{font-weight:600;font-size:.875rem;color:#e4e4e7;}
4217
- .app-link-scopes{font-size:.6875rem;color:#71717a;margin-top:1px;}
4218
- .empty{color:#71717a;text-align:center;padding:28px 0;font-size:.875rem;}
4219
- .page-footer{margin-top:20px;text-align:center;font-size:.6875rem;color:#3f3f46;}
4255
+ border:1px solid #0a3300;border-radius:8px;background:#000;
4256
+ text-decoration:none;color:inherit;margin-bottom:8px;transition:border-color .15s;
4257
+ }
4258
+ .app-link:hover{border-color:#33ff00;}
4259
+ .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
4260
+ .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
4261
+ .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
4220
4262
  `;
4263
+ var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
4221
4264
  function emuBar(service) {
4265
+ const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
4222
4266
  return `<div class="emu-bar">
4223
- <span class="emu-badge">emulator</span>
4224
- <span class="emu-bar-title">emulate</span>
4225
- ${service ? `<span class="emu-bar-service">${escapeHtml(service)}</span>` : ""}
4267
+ <span class="emu-bar-title">${title}</span>
4268
+ <nav class="emu-bar-links">
4269
+ <a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
4270
+ <a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
4271
+ <a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
4272
+ </nav>
4226
4273
  </div>`;
4227
4274
  }
4228
4275
  function head(title) {
@@ -4239,27 +4286,27 @@ function renderCardPage(title, subtitle, body, service) {
4239
4286
  return `${head(title)}
4240
4287
  <body>
4241
4288
  ${emuBar(service)}
4242
- <div class="card-wrap">
4243
- <div class="card">
4289
+ <div class="content">
4290
+ <div class="content-inner">
4244
4291
  <div class="card-title">${escapeHtml(title)}</div>
4245
4292
  <div class="card-subtitle">${subtitle}</div>
4246
4293
  ${body}
4247
- <div class="card-footer">emulate</div>
4248
4294
  </div>
4249
4295
  </div>
4296
+ ${POWERED_BY}
4250
4297
  </body></html>`;
4251
4298
  }
4252
4299
  function renderErrorPage(title, message2, service) {
4253
4300
  return `${head(title)}
4254
4301
  <body>
4255
4302
  ${emuBar(service)}
4256
- <div class="card-wrap">
4257
- <div class="card error-card">
4303
+ <div class="content">
4304
+ <div class="content-inner error-card">
4258
4305
  <div class="error-title">${escapeHtml(title)}</div>
4259
4306
  <div class="error-msg">${escapeHtml(message2)}</div>
4260
- <div class="card-footer">emulate</div>
4261
4307
  </div>
4262
4308
  </div>
4309
+ ${POWERED_BY}
4263
4310
  </body></html>`;
4264
4311
  }
4265
4312
  function renderSettingsPage(title, sidebarHtml, bodyHtml, service) {
@@ -4270,7 +4317,7 @@ ${emuBar(service)}
4270
4317
  <nav class="settings-sidebar">${sidebarHtml}</nav>
4271
4318
  <div class="settings-main">${bodyHtml}</div>
4272
4319
  </div>
4273
- <div class="page-footer">emulate</div>
4320
+ ${POWERED_BY}
4274
4321
  </body></html>`;
4275
4322
  }
4276
4323
  function renderUserButton(opts) {
@@ -16363,7 +16410,7 @@ function oauthRoutes2({ app, store, baseUrl, tokenMap }) {
16363
16410
  </form>
16364
16411
  </div>
16365
16412
  <ul class="perm-list">
16366
- ${permRows || '<li style="color:#71717a">No specific permissions granted.</li>'}
16413
+ ${permRows || '<li style="color:#1a8c00">No specific permissions granted.</li>'}
16367
16414
  </ul>
16368
16415
  </div>
16369
16416
 
@@ -17221,7 +17268,7 @@ var googlePlugin = {
17221
17268
 
17222
17269
  // src/commands/start.ts
17223
17270
  import { serve } from "@hono/node-server";
17224
- import { readFileSync, existsSync } from "fs";
17271
+ import { readFileSync as readFileSync2, existsSync } from "fs";
17225
17272
  import { resolve } from "path";
17226
17273
  import { parse as parseYaml } from "yaml";
17227
17274
  import { createRequire } from "module";
@@ -17235,7 +17282,7 @@ function loadSeedConfig(seedPath) {
17235
17282
  console.error(`Seed file not found: ${fullPath}`);
17236
17283
  process.exit(1);
17237
17284
  }
17238
- const content = readFileSync(fullPath, "utf-8");
17285
+ const content = readFileSync2(fullPath, "utf-8");
17239
17286
  try {
17240
17287
  const config = fullPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content);
17241
17288
  return { config, source: seedPath };
@@ -17255,7 +17302,7 @@ function loadSeedConfig(seedPath) {
17255
17302
  for (const file of autoFiles) {
17256
17303
  const fullPath = resolve(file);
17257
17304
  if (existsSync(fullPath)) {
17258
- const content = readFileSync(fullPath, "utf-8");
17305
+ const content = readFileSync2(fullPath, "utf-8");
17259
17306
  try {
17260
17307
  const config = fullPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content);
17261
17308
  return { config, source: file };
@@ -17274,7 +17321,7 @@ var SERVICE_PLUGINS = {
17274
17321
  };
17275
17322
  var ALL_SERVICES = Object.keys(SERVICE_PLUGINS);
17276
17323
  function inferServicesFromConfig(config) {
17277
- const found = Object.keys(config).filter((k) => k in SERVICE_PLUGINS);
17324
+ const found = ALL_SERVICES.filter((k) => k in config);
17278
17325
  return found.length > 0 ? found : null;
17279
17326
  }
17280
17327
  function startCommand(options) {