From 39d1d654eb71b6dd3e55291387938b1aaf80aa73 Mon Sep 17 00:00:00 2001 From: tmm Date: Fri, 12 Jun 2026 17:59:37 -0400 Subject: [PATCH] Add emulate.config.ts support --- README.md | 8 +- apps/web/app/docs/configuration/page.mdx | 29 ++- apps/web/app/docs/programmatic-api/page.mdx | 27 +++ packages/emulate/package.json | 13 +- packages/emulate/src/__tests__/config.test.ts | 50 ++++ packages/emulate/src/api.ts | 3 +- packages/emulate/src/commands/start.ts | 152 ++++++------ packages/emulate/src/config.ts | 126 ++++++++++ packages/emulate/src/index.ts | 2 + pnpm-lock.yaml | 219 +++++++++--------- skills/emulate/SKILL.md | 43 +++- 11 files changed, 478 insertions(+), 194 deletions(-) create mode 100644 packages/emulate/src/__tests__/config.test.ts create mode 100644 packages/emulate/src/config.ts diff --git a/README.md b/README.md index 00a6e1e9..890d563d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ npx emulate --port 3000 # Use a seed config file npx emulate --seed config.yaml +# Use a TypeScript config file with custom plugins +npx emulate --config emulate.config.ts + # Generate a starter config npx emulate init @@ -49,6 +52,7 @@ npx emulate list |------|---------|-------------| | `-p, --port` | `4000` | Base port (auto-increments per service) | | `-s, --service` | all | Comma-separated services to enable | +| `--config` | auto-detect | Path to an `emulate.config.ts` / `.js` config file | | `--seed` | auto-detect | Path to seed config (YAML or JSON) | | `--base-url` | none | Override advertised base URL (supports `{service}` template) | | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) | @@ -156,7 +160,9 @@ afterAll(() => Promise.all([github.close(), vercel.close()])) ## Configuration -Configuration is optional. The CLI auto-detects config files in this order: `emulate.config.yaml` / `.yml`, `emulate.config.json`, `service-emulator.config.yaml` / `.yml`, `service-emulator.config.json`. Or pass `--seed ` explicitly. Run `npx emulate init` to generate a starter file. +Configuration is optional. The CLI auto-detects config files in this order: `emulate.config.ts`, `emulate.config.mts`, `emulate.config.js`, `emulate.config.mjs`, `emulate.config.yaml` / `.yml`, `emulate.config.json`, `service-emulator.config.yaml` / `.yml`, `service-emulator.config.json`. Pass `--config ` for executable config or `--seed ` for YAML or JSON seed data. Run `npx emulate init` to generate a starter seed file. + +Use `emulate.config.ts` when you need custom plugins, custom ports, or local endpoint additions. Use YAML or JSON when you only need seed data. ```yaml tokens: diff --git a/apps/web/app/docs/configuration/page.mdx b/apps/web/app/docs/configuration/page.mdx index e1155669..6a040dff 100644 --- a/apps/web/app/docs/configuration/page.mdx +++ b/apps/web/app/docs/configuration/page.mdx @@ -1,6 +1,6 @@ # Configuration -Configuration is optional. All services start with sensible defaults. To customize seed data, create `emulate.config.yaml` in your project root (or pass `--seed`): +Configuration is optional. All services start with sensible defaults. To customize seed data, create `emulate.config.yaml` in your project root or pass `--seed`: ```yaml tokens: @@ -18,6 +18,33 @@ tokens: - user ``` +Use `emulate.config.ts` when you need custom plugins, custom ports, or local endpoint additions: + +```typescript +import { defineConfig } from 'emulate' +import { createPlugin } from 'emulate/core' +import { githubPlugin } from 'emulate/plugins' + +const github = createPlugin({ + name: 'github', + register(app, store, webhooks, baseUrl, tokenMap) { + githubPlugin.register(app, store, webhooks, baseUrl, tokenMap) + app.get('/extra', (c) => c.json({ ok: true })) + }, + seed: githubPlugin.seed, +}) + +export default defineConfig({ + services: { + github: { + plugin: github, + port: 4001, + seed: { users: [{ login: 'octocat' }] }, + }, + }, +}) +``` + ## Vercel Seed Config ```yaml diff --git a/apps/web/app/docs/programmatic-api/page.mdx b/apps/web/app/docs/programmatic-api/page.mdx index 10f02c79..e1f6b3d9 100644 --- a/apps/web/app/docs/programmatic-api/page.mdx +++ b/apps/web/app/docs/programmatic-api/page.mdx @@ -125,6 +125,33 @@ const { app } = createServer(plugin, { baseUrl: 'http://localhost:4000' }) const server = serve({ fetch: app.fetch, port: 4000 }) ``` +The CLI can load the same style of custom plugin from `emulate.config.ts`: + +```typescript +import { defineConfig } from 'emulate' +import { createPlugin } from 'emulate/core' +import { githubPlugin } from 'emulate/plugins' + +const github = createPlugin({ + name: 'github', + register(app, store, webhooks, baseUrl, tokenMap) { + githubPlugin.register(app, store, webhooks, baseUrl, tokenMap) + app.get('/extra', (c) => c.json({ ok: true })) + }, + seed: githubPlugin.seed, +}) + +export default defineConfig({ + services: { + github: { + plugin: github, + port: 4001, + seed: { users: [{ login: 'octocat' }] }, + }, + }, +}) +``` + ## Scoped packages Each emulator is published as its own `@emulators/*` package. Install only the ones you need: diff --git a/packages/emulate/package.json b/packages/emulate/package.json index 9d9dd392..550aef36 100644 --- a/packages/emulate/package.json +++ b/packages/emulate/package.json @@ -62,22 +62,23 @@ "dependencies": { "@emulators/core": "workspace:*", "commander": "^14", + "jiti": "^2.7.0", "picocolors": "^1.1.1", "yaml": "^2" }, "devDependencies": { - "@emulators/github": "workspace:*", "@emulators/apple": "workspace:*", - "@emulators/microsoft": "workspace:*", - "@emulators/okta": "workspace:*", "@emulators/aws": "workspace:*", + "@emulators/clerk": "workspace:*", + "@emulators/github": "workspace:*", "@emulators/google": "workspace:*", + "@emulators/microsoft": "workspace:*", "@emulators/mongoatlas": "workspace:*", - "@emulators/slack": "workspace:*", - "@emulators/vercel": "workspace:*", + "@emulators/okta": "workspace:*", "@emulators/resend": "workspace:*", + "@emulators/slack": "workspace:*", "@emulators/stripe": "workspace:*", - "@emulators/clerk": "workspace:*", + "@emulators/vercel": "workspace:*", "tsup": "^8", "typescript": "^5.7" } diff --git a/packages/emulate/src/__tests__/config.test.ts b/packages/emulate/src/__tests__/config.test.ts new file mode 100644 index 00000000..c8af6bb0 --- /dev/null +++ b/packages/emulate/src/__tests__/config.test.ts @@ -0,0 +1,50 @@ +import { mkdtempSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { afterEach, describe, expect, it } from "vitest"; +import { loadConfig } from "../config.js"; + +describe("loadConfig", () => { + let tempDir: string | null = null; + + afterEach(() => { + if (tempDir) { + rmSync(tempDir, { recursive: true, force: true }); + tempDir = null; + } + }); + + it("loads an emulate.config.ts file with a custom plugin", async () => { + tempDir = mkdtempSync(join(tmpdir(), "emulate-config-")); + const configPath = join(tempDir, "emulate.config.ts"); + writeFileSync( + configPath, + ` + export default { + port: 14100, + tokens: { test_token: { login: 'tester', scopes: ['read'] } }, + services: { + custom: { + port: 14101, + seed: { message: 'hello' }, + plugin: { + name: 'custom', + register(app) { + app.get('/ping', (c) => c.json({ ok: true })) + }, + }, + }, + }, + } + `, + "utf-8", + ); + + const loaded = await loadConfig({ configPath }); + + expect(loaded?.runtimeConfig?.port).toBe(14100); + expect(loaded?.runtimeConfig?.services?.custom?.port).toBe(14101); + expect(loaded?.runtimeConfig?.services?.custom?.plugin?.name).toBe("custom"); + expect(loaded?.seedConfig.tokens?.test_token.login).toBe("tester"); + }); +}); diff --git a/packages/emulate/src/api.ts b/packages/emulate/src/api.ts index 79ea1fbe..41f9861e 100644 --- a/packages/emulate/src/api.ts +++ b/packages/emulate/src/api.ts @@ -1,8 +1,9 @@ -import { createServer, serve, type AppKeyResolver, type Store } from "@emulators/core"; +import { createServer, serve, type AppKeyResolver } from "@emulators/core"; import { SERVICE_REGISTRY } from "./registry.js"; export type { ServiceName } from "./registry.js"; import type { ServiceName } from "./registry.js"; import { resolveBaseUrl } from "./base-url.js"; +export { defineConfig, type EmulateConfig, type EmulateServiceConfig } from "./config.js"; export interface SeedConfig { tokens?: Record; diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index 10a95e2f..6f89b27b 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -1,11 +1,9 @@ import { createServer, serve, type AppKeyResolver, type Store } from "@emulators/core"; -import { SERVICE_REGISTRY, SERVICE_NAMES, type ServiceName } from "../registry.js"; -import { readFileSync, existsSync } from "fs"; -import { resolve } from "path"; -import { parse as parseYaml } from "yaml"; +import { SERVICE_REGISTRY, SERVICE_NAMES, type LoadedService, type ServiceEntry, type ServiceName } from "../registry.js"; import pc from "picocolors"; import { ensurePortless, registerAliases, removeAliases, portlessBaseUrl, type PortlessAlias } from "../portless.js"; import { resolveBaseUrl } from "../base-url.js"; +import { loadConfig, type EmulateConfig, type EmulateServiceConfig, type SeedConfig } from "../config.js"; declare const PKG_VERSION: string; const pkg = { version: PKG_VERSION }; @@ -13,84 +11,84 @@ const pkg = { version: PKG_VERSION }; export interface StartOptions { port: number; service?: string; + config?: string; seed?: string; baseUrl?: string; portless?: boolean; } -interface SeedConfig { - tokens?: Record; - [service: string]: unknown; -} - -interface LoadResult { - config: SeedConfig; - source: string; +function inferServicesFromConfig(config: SeedConfig): ServiceName[] | null { + const found = SERVICE_NAMES.filter((k) => k in config); + return found.length > 0 ? [...found] : null; } -function loadSeedConfig(seedPath?: string): LoadResult | null { - if (seedPath) { - const fullPath = resolve(seedPath); - if (!existsSync(fullPath)) { - console.error(`Seed file not found: ${fullPath}`); - process.exit(1); - } - const content = readFileSync(fullPath, "utf-8"); - try { - const config = fullPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content); - return { config, source: seedPath }; - } catch (err) { - console.error(`Failed to parse ${seedPath}: ${err instanceof Error ? err.message : err}`); - process.exit(1); - } - } - - const autoFiles = [ - "emulate.config.yaml", - "emulate.config.yml", - "emulate.config.json", - "service-emulator.config.yaml", - "service-emulator.config.yml", - "service-emulator.config.json", - ]; - - for (const file of autoFiles) { - const fullPath = resolve(file); - if (existsSync(fullPath)) { - const content = readFileSync(fullPath, "utf-8"); - try { - const config = fullPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content); - return { config, source: file }; - } catch (err) { - console.error(`Failed to parse ${file}: ${err instanceof Error ? err.message : err}`); - process.exit(1); +function fallbackEntry(name: string, plugin: EmulateServiceConfig["plugin"]): ServiceEntry { + return { + label: `${name} custom emulator`, + endpoints: "custom", + async load() { + if (!plugin) { + throw new Error(`Service "${name}" must define a plugin in emulate.config.ts`); } - } - } + return { plugin }; + }, + defaultFallback() { + return { login: "admin", id: 1, scopes: [] }; + }, + initConfig: {}, + }; +} - return null; +async function loadService( + name: string, + entry: ServiceEntry, + serviceConfig: EmulateServiceConfig | undefined, +): Promise { + const builtIn = name in SERVICE_REGISTRY ? await entry.load() : null; + if (!serviceConfig?.plugin) return builtIn ?? (await entry.load()); + + return { + plugin: serviceConfig.plugin, + seedFromConfig: serviceConfig.seedFromConfig ?? builtIn?.seedFromConfig, + createAppKeyResolver: serviceConfig.createAppKeyResolver ?? builtIn?.createAppKeyResolver, + }; } -function inferServicesFromConfig(config: SeedConfig): ServiceName[] | null { - const found = SERVICE_NAMES.filter((k) => k in config); - return found.length > 0 ? [...found] : null; +function serviceSeedConfig( + seedConfig: SeedConfig | null, + runtimeConfig: EmulateConfig | undefined, + service: string, +): Record | undefined { + return runtimeConfig?.services?.[service]?.seed ?? (seedConfig?.[service] as Record | undefined); } export async function startCommand(options: StartOptions): Promise { - const { port: basePort } = options; + let loaded; + try { + loaded = await loadConfig({ configPath: options.config, seedPath: options.seed }); + } catch (err) { + console.error(err instanceof Error ? err.message : err); + process.exit(1); + } + + const runtimeConfig = loaded?.runtimeConfig; + const basePort = runtimeConfig?.port ?? options.port; + const configBaseUrl = runtimeConfig?.baseUrl; - if (options.portless && options.baseUrl) { + if (options.portless && (options.baseUrl || configBaseUrl)) { console.error("--portless and --base-url are mutually exclusive."); process.exit(1); } - const loaded = loadSeedConfig(options.seed); - const seedConfig = loaded?.config ?? null; + const seedConfig = loaded?.seedConfig ?? null; const configSource = loaded?.source ?? null; + const runtimeServices = runtimeConfig?.services ?? {}; - let services: ServiceName[]; + let services: string[]; if (options.service) { - services = options.service.split(",").map((s) => s.trim()) as ServiceName[]; + services = options.service.split(",").map((s) => s.trim()); + } else if (Object.keys(runtimeServices).length > 0) { + services = Object.keys(runtimeServices); } else if (seedConfig) { services = inferServicesFromConfig(seedConfig) ?? [...SERVICE_NAMES]; } else { @@ -98,7 +96,7 @@ export async function startCommand(options: StartOptions): Promise { } for (const svc of services) { - if (!SERVICE_REGISTRY[svc]) { + if (!SERVICE_REGISTRY[svc as ServiceName] && !runtimeServices[svc]?.plugin) { console.error(`Unknown service: ${svc}`); process.exit(1); } @@ -119,9 +117,10 @@ export async function startCommand(options: StartOptions): Promise { } interface PreparedService { - svc: ServiceName; - entry: (typeof SERVICE_REGISTRY)[ServiceName]; - loadedSvc: Awaited>; + svc: string; + entry: ServiceEntry; + serviceConfig: EmulateServiceConfig | undefined; + loadedSvc: LoadedService; svcSeedConfig: Record | undefined; port: number; baseUrl: string; @@ -132,24 +131,27 @@ export async function startCommand(options: StartOptions): Promise { for (let i = 0; i < services.length; i++) { const svc = services[i]; - const entry = SERVICE_REGISTRY[svc]; - const loadedSvc = await entry.load(); + const serviceConfig = runtimeServices[svc]; + const entry = SERVICE_REGISTRY[svc as ServiceName] ?? fallbackEntry(svc, serviceConfig?.plugin); + const loadedSvc = await loadService(svc, entry, serviceConfig); - const svcSeedConfig = seedConfig?.[svc] as Record | undefined; - const port = (svcSeedConfig?.port as number | undefined) ?? basePort + i; + const svcSeedConfig = serviceSeedConfig(seedConfig, runtimeConfig, svc); + const port = serviceConfig?.port ?? (svcSeedConfig?.port as number | undefined) ?? basePort + i; if (options.portless) { portlessAliases.push({ name: `${svc}.emulate`, port }); } const seedBaseUrl = - typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 - ? svcSeedConfig.baseUrl - : undefined; - const effectiveBaseUrl = options.portless ? portlessBaseUrl(svc) : options.baseUrl; + typeof serviceConfig?.baseUrl === "string" && serviceConfig.baseUrl.length > 0 + ? serviceConfig.baseUrl + : typeof svcSeedConfig?.baseUrl === "string" && svcSeedConfig.baseUrl.length > 0 + ? svcSeedConfig.baseUrl + : undefined; + const effectiveBaseUrl = options.portless ? portlessBaseUrl(svc) : (options.baseUrl ?? configBaseUrl); const baseUrl = resolveBaseUrl({ service: svc, port, baseUrl: effectiveBaseUrl, seedBaseUrl }); - prepared.push({ svc, entry, loadedSvc, svcSeedConfig, port, baseUrl }); + prepared.push({ svc, entry, serviceConfig, loadedSvc, svcSeedConfig, port, baseUrl }); } if (portlessAliases.length > 0) { @@ -160,7 +162,7 @@ export async function startCommand(options: StartOptions): Promise { const stores: Store[] = []; const httpServers: ReturnType[] = []; - for (const { svc, entry, loadedSvc, svcSeedConfig, port, baseUrl } of prepared) { + for (const { svc, entry, serviceConfig, loadedSvc, svcSeedConfig, port, baseUrl } of prepared) { serviceUrls.push({ name: svc, url: baseUrl }); // eslint-disable-next-line prefer-const -- reassigned after closure captures it @@ -169,7 +171,7 @@ export async function startCommand(options: StartOptions): Promise { ? (appId) => cachedResolver!(appId) : undefined; - const fallbackUser = entry.defaultFallback(svcSeedConfig); + const fallbackUser = serviceConfig?.defaultFallback?.(svcSeedConfig) ?? entry.defaultFallback(svcSeedConfig); const { app, store, webhooks } = createServer(loadedSvc.plugin, { port, diff --git a/packages/emulate/src/config.ts b/packages/emulate/src/config.ts new file mode 100644 index 00000000..504b5568 --- /dev/null +++ b/packages/emulate/src/config.ts @@ -0,0 +1,126 @@ +import { existsSync, readFileSync } from "fs"; +import { resolve } from "path"; +import { createJiti } from "jiti"; +import { parse as parseYaml } from "yaml"; +import type { AppKeyResolver, AuthFallback, ServicePlugin, Store, WebhookDispatcher } from "@emulators/core"; + +export interface SeedConfig { + tokens?: Record; + [service: string]: unknown; +} + +export interface EmulateServiceConfig { + plugin?: ServicePlugin; + port?: number; + baseUrl?: string; + seed?: Record; + seedFromConfig?(store: Store, baseUrl: string, config: unknown, webhooks?: WebhookDispatcher): void; + createAppKeyResolver?(store: Store): AppKeyResolver; + defaultFallback?(svcSeedConfig?: Record): AuthFallback; +} + +export interface EmulateConfig { + port?: number; + baseUrl?: string; + tokens?: Record; + seed?: SeedConfig; + services?: Record; +} + +export interface LoadedConfig { + source: string; + seedConfig: SeedConfig; + runtimeConfig?: EmulateConfig; +} + +export function defineConfig(config: T): T { + return config; +} + +export async function loadConfig(options: { configPath?: string; seedPath?: string } = {}): Promise { + if (options.configPath && options.seedPath) { + throw new Error("--config and --seed are mutually exclusive"); + } + + if (options.configPath) { + return loadConfigFile(options.configPath); + } + + if (options.seedPath) { + return loadSeedFile(options.seedPath); + } + + const autoFiles = [ + "emulate.config.ts", + "emulate.config.mts", + "emulate.config.js", + "emulate.config.mjs", + "emulate.config.yaml", + "emulate.config.yml", + "emulate.config.json", + "service-emulator.config.yaml", + "service-emulator.config.yml", + "service-emulator.config.json", + ]; + + for (const file of autoFiles) { + if (!existsSync(resolve(file))) continue; + return isCodeConfig(file) ? loadConfigFile(file) : loadSeedFile(file); + } + + return null; +} + +async function loadConfigFile(configPath: string): Promise { + const fullPath = resolve(configPath); + if (!existsSync(fullPath)) { + throw new Error(`Config file not found: ${fullPath}`); + } + + if (!isCodeConfig(fullPath)) { + return loadSeedFile(configPath); + } + + try { + const jiti = createJiti(import.meta.url); + const config = await jiti.import(fullPath, { default: true }); + if (!config || typeof config !== "object" || Array.isArray(config)) { + throw new Error("config must export an object"); + } + + return { + source: configPath, + seedConfig: normalizeRuntimeSeedConfig(config), + runtimeConfig: config, + }; + } catch (err) { + throw new Error(`Failed to load ${configPath}: ${err instanceof Error ? err.message : err}`); + } +} + +function loadSeedFile(seedPath: string): LoadedConfig { + const fullPath = resolve(seedPath); + if (!existsSync(fullPath)) { + throw new Error(`Seed file not found: ${fullPath}`); + } + + try { + const content = readFileSync(fullPath, "utf-8"); + const config = fullPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content); + return { seedConfig: config, source: seedPath }; + } catch (err) { + throw new Error(`Failed to parse ${seedPath}: ${err instanceof Error ? err.message : err}`); + } +} + +function normalizeRuntimeSeedConfig(config: EmulateConfig): SeedConfig { + const seedConfig: SeedConfig = { ...(config.seed ?? {}) }; + if (config.tokens) { + seedConfig.tokens = config.tokens; + } + return seedConfig; +} + +function isCodeConfig(file: string): boolean { + return /\.m?[jt]s$/.test(file); +} diff --git a/packages/emulate/src/index.ts b/packages/emulate/src/index.ts index 51b40a09..794ec386 100644 --- a/packages/emulate/src/index.ts +++ b/packages/emulate/src/index.ts @@ -20,6 +20,7 @@ program .description("Start the emulator server") .option("-p, --port ", "Base port", defaultPort) .option("-s, --service ", "Comma-separated services to enable") + .option("--config ", "Path to config file") .option("--seed ", "Path to seed config file") .option("--base-url ", "Override advertised base URL (supports {service} template)") .option("--portless", "Serve over HTTPS via portless (auto-registers aliases)") @@ -32,6 +33,7 @@ program await startCommand({ port, service: opts.service, + config: opts.config, seed: opts.seed, baseUrl: opts.baseUrl, portless: opts.portless, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 591ea1de..83e8414e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 22.19.17 eslint: specifier: ^9.39.4 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + version: 10.1.8(eslint@9.39.4(jiti@2.7.0)) prettier: specifier: ^3.8.1 version: 3.8.1 @@ -28,10 +28,10 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.58.0 - version: 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) apps/web: dependencies: @@ -125,10 +125,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -189,10 +189,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -244,10 +244,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -308,10 +308,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -360,10 +360,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -424,10 +424,10 @@ importers: version: 19.2.3(@types/react@19.2.14) eslint: specifier: ^9 - version: 9.39.4(jiti@2.6.1) + version: 9.39.4(jiti@2.7.0) eslint-config-next: specifier: 16.2.0 - version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + version: 16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.2 @@ -443,7 +443,7 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 @@ -459,13 +459,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/aws: dependencies: @@ -481,13 +481,13 @@ importers: version: 3.1031.0 tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/clerk: dependencies: @@ -500,13 +500,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/core: dependencies: @@ -516,13 +516,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/github: dependencies: @@ -532,13 +532,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/google: dependencies: @@ -551,13 +551,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/microsoft: dependencies: @@ -570,13 +570,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/mongoatlas: dependencies: @@ -586,13 +586,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/okta: dependencies: @@ -605,13 +605,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/resend: dependencies: @@ -621,13 +621,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/slack: dependencies: @@ -640,13 +640,13 @@ importers: version: 7.16.0 tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/stripe: dependencies: @@ -656,13 +656,13 @@ importers: devDependencies: tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/@emulators/vercel: dependencies: @@ -675,13 +675,13 @@ importers: version: 2.4.0 tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 vitest: specifier: ^4.1.0 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) packages/emulate: dependencies: @@ -691,6 +691,9 @@ importers: commander: specifier: ^14 version: 14.0.3 + jiti: + specifier: ^2.7.0 + version: 2.7.0 picocolors: specifier: ^1.1.1 version: 1.1.1 @@ -736,7 +739,7 @@ importers: version: link:../@emulators/vercel tsup: specifier: ^8 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.7 version: 5.9.3 @@ -4542,6 +4545,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} @@ -6830,9 +6837,9 @@ snapshots: '@esbuild/win32-x64@0.27.4': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))': dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -8729,15 +8736,15 @@ snapshots: '@types/unist@3.0.3': {} - '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/type-utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.58.0 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -8745,14 +8752,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.58.0 '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.58.0 debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8775,13 +8782,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -8804,13 +8811,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.58.0 '@typescript-eslint/types': 8.58.0 '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -8918,13 +8925,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.3(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3))': + '@vitest/mocker@4.1.3(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.3 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + vite: 8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.3': dependencies: @@ -9718,18 +9725,18 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + eslint-config-next@16.2.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.2.0 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.4(jiti@2.7.0)) globals: 16.4.0 - typescript-eslint: 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: @@ -9738,9 +9745,9 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.7.0)): dependencies: - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node@0.3.9: dependencies: @@ -9750,33 +9757,33 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) get-tsconfig: 4.13.6 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9785,9 +9792,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9799,13 +9806,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.4(jiti@2.7.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -9815,7 +9822,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -9824,18 +9831,18 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4(jiti@2.7.0)): dependencies: '@babel/core': 7.29.0 '@babel/parser': 7.29.2 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.3.6 zod-validation-error: 4.0.2(zod@4.3.6) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.39.4(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -9843,7 +9850,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.3.1 - eslint: 9.39.4(jiti@2.6.1) + eslint: 9.39.4(jiti@2.7.0) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -9868,9 +9875,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.6.1): + eslint@9.39.4(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 @@ -9905,7 +9912,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -10487,6 +10494,8 @@ snapshots: jiti@2.6.1: {} + jiti@2.7.0: {} + jose@6.2.2: {} joycon@3.1.1: {} @@ -11386,11 +11395,11 @@ snapshots: postal-mime@2.7.4: {} - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.3): + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.8)(yaml@2.8.3): dependencies: lilconfig: 3.1.3 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 postcss: 8.5.8 yaml: 2.8.3 @@ -12201,7 +12210,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.8)(typescript@5.9.3)(yaml@2.8.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.4) cac: 6.7.14 @@ -12212,7 +12221,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(yaml@2.8.3) + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.8)(yaml@2.8.3) resolve-from: 5.0.0 rollup: 4.59.0 source-map: 0.7.6 @@ -12286,13 +12295,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.4(jiti@2.6.1) + '@typescript-eslint/utils': 8.58.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) + eslint: 9.39.4(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -12428,7 +12437,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3): + vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -12439,16 +12448,16 @@ snapshots: '@types/node': 22.19.17 esbuild: 0.27.4 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 yaml: 2.8.3 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)): + vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@22.19.17)(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.3 - '@vitest/mocker': 4.1.3(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3)) + '@vitest/mocker': 4.1.3(vite@8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.3 '@vitest/runner': 4.1.3 '@vitest/snapshot': 4.1.3 @@ -12465,7 +12474,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.3) + vite: 8.0.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.17)(esbuild@0.27.4)(jiti@2.7.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 diff --git a/skills/emulate/SKILL.md b/skills/emulate/SKILL.md index fe1a85b1..1bb3bb06 100644 --- a/skills/emulate/SKILL.md +++ b/skills/emulate/SKILL.md @@ -41,6 +41,9 @@ npx emulate --port 3000 # Use a seed config file npx emulate --seed config.yaml +# Use a TypeScript config file with custom plugins +npx emulate --config emulate.config.ts + # Generate a starter config npx emulate init @@ -57,6 +60,7 @@ npx emulate list |------|---------|-------------| | `-p, --port` | `4000` | Base port (auto-increments per service) | | `-s, --service` | all | Comma-separated services to enable | +| `--config` | auto-detect | Path to an `emulate.config.ts` / `.js` config file | | `--seed` | auto-detect | Path to seed config (YAML or JSON) | | `--base-url` | none | Override advertised base URL (supports `{service}` template) | | `--portless` | off | Serve over HTTPS via portless (auto-registers aliases) | @@ -149,12 +153,41 @@ afterAll(() => Promise.all([github.close(), vercel.close()])) Configuration is optional. The CLI auto-detects config files in this order: -1. `emulate.config.yaml` / `.yml` -2. `emulate.config.json` -3. `service-emulator.config.yaml` / `.yml` -4. `service-emulator.config.json` +1. `emulate.config.ts` / `.mts` +2. `emulate.config.js` / `.mjs` +3. `emulate.config.yaml` / `.yml` +4. `emulate.config.json` +5. `service-emulator.config.yaml` / `.yml` +6. `service-emulator.config.json` + +Use `emulate.config.ts` for custom plugins or endpoint additions: + +```typescript +import { defineConfig } from 'emulate' +import { createPlugin } from 'emulate/core' +import { githubPlugin } from 'emulate/plugins' + +const github = createPlugin({ + name: 'github', + register(app, store, webhooks, baseUrl, tokenMap) { + githubPlugin.register(app, store, webhooks, baseUrl, tokenMap) + app.get('/extra', (c) => c.json({ ok: true })) + }, + seed: githubPlugin.seed, +}) + +export default defineConfig({ + services: { + github: { + plugin: github, + port: 4001, + seed: { users: [{ login: 'octocat' }] }, + }, + }, +}) +``` -Or pass `--seed ` explicitly. Run `npx emulate init` to generate a starter file. +Pass `--config ` for executable config or `--seed ` for YAML or JSON seed data. Run `npx emulate init` to generate a starter seed file. ### Config Structure