api-emulator 0.5.2 → 0.7.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.
package/README.md CHANGED
@@ -4,14 +4,14 @@
4
4
 
5
5
  <h1 align="center">api-emulator</h1>
6
6
 
7
- <p align="center">Fake real APIs locally so your app can test integrations without touching production, sandboxes, or someone else's server.</p>
7
+ <p align="center">Local staging for API integrations, agent runs, and CI without touching production, sandboxes, or someone else's server.</p>
8
8
 
9
- `api-emulator` is a local app store for fake APIs. Run GitHub, Stripe, Resend, and plugin powered providers on localhost, seed state, inspect behavior, reset data, and test your app in one place.
9
+ `api-emulator` is a local app store for stateful API clones. Run GitHub, Stripe, Resend, and plugin powered providers on localhost, seed state, inspect behavior, reset data, and test your app in one place.
10
10
 
11
11
  ## Why use it?
12
12
 
13
13
  - Test API integrations locally without real provider credentials.
14
- - Run multiple fake services together, with shared state, auth, webhooks, seed data, and resets.
14
+ - Run multiple provider clones together, with shared state, auth, webhooks, seed data, and resets.
15
15
  - Keep provider behavior in plugins so public, private, and internal APIs can live outside your app.
16
16
 
17
17
  ## Quick start
@@ -19,6 +19,7 @@
19
19
  ```bash
20
20
  npx -p api-emulator api
21
21
  npx -p api-emulator api --service github,stripe,resend
22
+ npx -p api-emulator api --no-notify
22
23
  ```
23
24
 
24
25
  Then point your app at the local provider URLs:
@@ -48,24 +49,37 @@ npx -p api-emulator api init
48
49
  npx -p api-emulator api list
49
50
  ```
50
51
 
52
+ ## Agent instructions
53
+
54
+ For coding agents, use the short agent-facing reference at <https://api-emulator.jsj.sh/agent.txt>. Copy this into your agent prompt:
55
+
56
+ <details>
57
+ <summary>Copy agent instruction</summary>
58
+
59
+ ```text
60
+ Read https://api-emulator.jsj.sh/agent.txt before using api-emulator.
61
+ ```
62
+
63
+ </details>
64
+
51
65
  ## Use in tests
52
66
 
53
67
  ```ts
54
- import { createEmulator } from 'api-emulator'
68
+ import { createEmulator } from "api-emulator";
55
69
 
56
- const github = await createEmulator({ service: 'github', port: 4001 })
57
- process.env.GITHUB_API_BASE = github.url
70
+ const github = await createEmulator({ service: "github", port: 4001 });
71
+ process.env.GITHUB_API_BASE = github.url;
58
72
 
59
- afterEach(() => github.reset())
60
- afterAll(() => github.close())
73
+ afterEach(() => github.reset());
74
+ afterAll(() => github.close());
61
75
  ```
62
76
 
63
77
  Capture and replay a stable fixture after a stochastic or stateful run:
64
78
 
65
79
  ```ts
66
- const fixture = github.exportFixture({ metadata: { name: 'pull-request-flow' } })
80
+ const fixture = github.exportFixture({ metadata: { name: "pull-request-flow" } });
67
81
 
68
- github.resetToFixture(fixture)
82
+ github.resetToFixture(fixture);
69
83
  ```
70
84
 
71
85
  ## Plugins
@@ -85,17 +99,42 @@ npx -p api-emulator api --plugin ./api-emulator-plugins/@posthog/api-emulator.mj
85
99
 
86
100
  The installer auto discovers sibling `api-emulator-plugins` and `api-emulator-internal` checkouts. Set `API_EMULATOR_PLUGIN_CATALOGS=/path/to/shelf,/path/to/internal` to add more shelves.
87
101
 
102
+ Sanity check a plugin before installing or loading it:
103
+
104
+ ```bash
105
+ npx -p api-emulator api validate-plugin posthog
106
+ npx -p api-emulator api validate-plugin ./api-emulator-plugins/@posthog/api-emulator.mjs
107
+ ```
108
+
109
+ Scaffold a local provider clone and catalog entry:
110
+
111
+ ```bash
112
+ npx -p api-emulator api clone create internal-billing
113
+ ```
114
+
115
+ The scaffold includes starter fidelity metadata so `api list` can show whether a provider is still a stub, partially covered, or contract-backed. `api plugin create` remains available as a compatibility alias. Generated scaffolds and agent skills are tracked in `.api-emulator/manifest.json` so reruns avoid overwriting local edits unless you pass `--yes`.
116
+
117
+ Install local agent skills for plugin authoring and runtime workflows:
118
+
119
+ ```bash
120
+ npx -p api-emulator api init --skills-only --agents agents
121
+ ```
122
+
123
+ Use `--agents user-agents` to install into `~/.agents/skills`.
124
+
125
+ On macOS, api-emulator shows a best effort notification when the server is ready. Use `--no-notify` to silence it. Add `--notify` to get completion notifications for `init`, `install`, `validate-plugin`, and plugin scaffold commands.
126
+
88
127
  A plugin exports a `ServicePlugin`:
89
128
 
90
129
  ```ts
91
- import type { ServicePlugin } from '@api-emulator/core'
130
+ import type { ServicePlugin } from "@api-emulator/core";
92
131
 
93
132
  export const plugin: ServicePlugin = {
94
- name: 'internal-billing',
133
+ name: "internal-billing",
95
134
  register(app) {
96
- app.get('/v1/customers', (c) => c.json({ data: [] }))
135
+ app.get("/v1/customers", (c) => c.json({ data: [] }));
97
136
  },
98
- }
137
+ };
99
138
  ```
100
139
 
101
140
  ## Next.js embedded mode
@@ -105,21 +144,21 @@ npm install @api-emulator/adapter-next @api-emulator/core
105
144
  ```
106
145
 
107
146
  ```ts
108
- import { createEmulateHandler } from '@api-emulator/adapter-next'
109
- import type { ServicePlugin } from '@api-emulator/core'
147
+ import { createApiEmulatorHandler } from "@api-emulator/adapter-next";
148
+ import type { ServicePlugin } from "@api-emulator/core";
110
149
 
111
150
  const internalPlugin: ServicePlugin = {
112
- name: 'internal',
151
+ name: "internal",
113
152
  register(app) {
114
- app.get('/health', (c) => c.json({ ok: true }))
153
+ app.get("/health", (c) => c.json({ ok: true }));
115
154
  },
116
- }
155
+ };
117
156
 
118
- export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
157
+ export const { GET, POST, PUT, PATCH, DELETE } = createApiEmulatorHandler({
119
158
  services: {
120
159
  internal: { emulator: { plugin: internalPlugin } },
121
160
  },
122
- })
161
+ });
123
162
  ```
124
163
 
125
164
  ## Configuration
@@ -138,7 +177,7 @@ github:
138
177
  name: The Octocat
139
178
  ```
140
179
 
141
- The CLI auto-detects `api-emulator.config.yaml`, `.yml`, and `.json`. Older `emulate.config.*` and `service-emulator.config.*` names still work as migration aliases.
180
+ The CLI auto-detects `api-emulator.config.yaml`, `.yml`, and `.json`.
142
181
 
143
182
  ## Examples
144
183
 
package/dist/api.js CHANGED
@@ -2,14 +2,13 @@
2
2
  import { isAbsolute, resolve } from "path";
3
3
 
4
4
  // src/plugin-manifest.ts
5
+ function formatPluginFidelity(fidelity) {
6
+ if (!fidelity) return "unrated";
7
+ if (typeof fidelity === "string") return fidelity;
8
+ return fidelity.level;
9
+ }
5
10
  function readPluginManifest(mod) {
6
- return {
7
- label: mod.label,
8
- endpoints: mod.endpoints,
9
- initConfig: mod.initConfig,
10
- contract: mod.contract,
11
- ...mod.manifest
12
- };
11
+ return mod.manifest ?? {};
13
12
  }
14
13
  function validatePluginManifest(manifest, pluginName) {
15
14
  if (manifest.name && manifest.name !== pluginName) {
@@ -37,6 +36,7 @@ async function loadExternalPluginModule(specifier) {
37
36
  name,
38
37
  label: manifest.label,
39
38
  endpoints: manifest.endpoints,
39
+ fidelity: formatPluginFidelity(manifest.fidelity),
40
40
  manifest,
41
41
  async load() {
42
42
  return {
@@ -98,7 +98,7 @@ function resolveBaseUrl(opts) {
98
98
  if (opts.baseUrl) {
99
99
  return opts.baseUrl.replace(/\{service\}/g, opts.service);
100
100
  }
101
- const envBaseUrl = process.env.API_EMULATOR_BASE_URL ?? process.env.EMULATE_BASE_URL;
101
+ const envBaseUrl = process.env.API_EMULATOR_BASE_URL;
102
102
  if (envBaseUrl) {
103
103
  return envBaseUrl.replace(/\{service\}/g, opts.service);
104
104
  }
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/external-plugin-adapter.ts","../src/plugin-manifest.ts","../src/plugin-lock.ts","../src/default-plugin-catalog.ts","../src/registry.ts","../src/base-url.ts","../src/service-runtime.ts","../src/api.ts"],"sourcesContent":["import type { ServicePlugin, Store, AuthFallback, WebhookDispatcher } from \"@api-emulator/core\";\nimport { isAbsolute, resolve } from \"path\";\nimport { readPluginManifest, validatePluginManifest, type PluginManifest } from \"./plugin-manifest.js\";\nimport type { PluginModule } from \"./plugin-types.js\";\n\nexport interface ExternalPluginModule {\n plugin?: ServicePlugin;\n default?: ServicePlugin;\n seedFromConfig?(store: Store, baseUrl: string, config: unknown, webhooks?: WebhookDispatcher): void;\n label?: string;\n endpoints?: string;\n manifest?: PluginManifest;\n contract?: unknown;\n defaultFallback?(svcSeedConfig?: Record<string, unknown>): AuthFallback;\n initConfig?: Record<string, unknown>;\n}\n\nexport async function loadExternalPluginModule(specifier: string): Promise<PluginModule> {\n const modulePath = specifier.startsWith(\".\") || isAbsolute(specifier) ? resolve(specifier) : specifier;\n\n const mod = (await import(modulePath)) as ExternalPluginModule;\n const plugin = mod.plugin ?? mod.default;\n if (!plugin || typeof plugin.register !== \"function\" || typeof plugin.name !== \"string\") {\n throw new Error(`Plugin \"${specifier}\" must export a ServicePlugin (as \"plugin\" or default export)`);\n }\n\n const name = plugin.name;\n const manifest = validatePluginManifest(readPluginManifest(mod), name);\n return {\n name,\n label: manifest.label,\n endpoints: manifest.endpoints,\n manifest,\n async load() {\n return {\n plugin,\n seedFromConfig: mod.seedFromConfig,\n };\n },\n defaultFallback: mod.defaultFallback ?? (() => ({ login: \"admin\", id: 1, scopes: [] })),\n initConfig: manifest.initConfig,\n };\n}\n\nexport async function loadExternalPlugin(specifier: string): Promise<{ name: string; entry: PluginModule }> {\n const pluginModule = await loadExternalPluginModule(specifier);\n return { name: pluginModule.name, entry: pluginModule };\n}\n","import type { AuthFallback } from \"@api-emulator/core\";\n\nexport interface PluginManifest {\n name?: string;\n label?: string;\n endpoints?: string;\n initConfig?: Record<string, unknown>;\n contract?: unknown;\n compatibility?: {\n apiEmulator?: string;\n };\n}\n\ninterface LegacyManifestFields {\n label?: string;\n endpoints?: string;\n initConfig?: Record<string, unknown>;\n contract?: unknown;\n defaultFallback?(svcSeedConfig?: Record<string, unknown>): AuthFallback;\n}\n\nexport function readPluginManifest(mod: { manifest?: PluginManifest } & LegacyManifestFields): PluginManifest {\n return {\n label: mod.label,\n endpoints: mod.endpoints,\n initConfig: mod.initConfig,\n contract: mod.contract,\n ...mod.manifest,\n };\n}\n\nexport function validatePluginManifest(\n manifest: PluginManifest,\n pluginName: string,\n): Required<Pick<PluginManifest, \"label\" | \"endpoints\" | \"initConfig\">> &\n Omit<PluginManifest, \"label\" | \"endpoints\" | \"initConfig\"> {\n if (manifest.name && manifest.name !== pluginName) {\n throw new Error(`Plugin manifest name \"${manifest.name}\" does not match plugin name \"${pluginName}\"`);\n }\n\n return {\n ...manifest,\n label: manifest.label ?? `${pluginName} (external plugin)`,\n endpoints: manifest.endpoints ?? \"\",\n initConfig: manifest.initConfig ?? {},\n };\n}\n","import { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { resolve } from \"path\";\n\nexport const PLUGIN_LOCK_FILE = \"api-emulator.lock\";\n\nexport interface PluginLockEntry {\n name: string;\n source: \"registry\" | \"specifier\";\n specifier: string;\n sourceId?: string;\n packageName?: string;\n version?: string;\n}\n\nexport interface PluginLock {\n version: 1;\n plugins: Record<string, PluginLockEntry>;\n}\n\nexport function createEmptyPluginLock(): PluginLock {\n return { version: 1, plugins: {} };\n}\n\nexport function readPluginLock(cwd = process.cwd()): PluginLock {\n const path = resolve(cwd, PLUGIN_LOCK_FILE);\n if (!existsSync(path)) return createEmptyPluginLock();\n\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as PluginLock;\n if (parsed.version !== 1 || typeof parsed.plugins !== \"object\" || parsed.plugins === null) {\n throw new Error(`Invalid ${PLUGIN_LOCK_FILE}`);\n }\n return parsed;\n}\n\nexport function writePluginLock(lock: PluginLock, cwd = process.cwd()): void {\n const sortedPlugins = Object.fromEntries(Object.entries(lock.plugins).sort(([a], [b]) => a.localeCompare(b)));\n const content = `${JSON.stringify({ version: 1, plugins: sortedPlugins }, null, 2)}\\n`;\n writeFileSync(resolve(cwd, PLUGIN_LOCK_FILE), content, \"utf-8\");\n}\n\nexport function getLockedPluginSpecifiers(cwd = process.cwd()): string[] {\n return Object.values(readPluginLock(cwd).plugins).map((entry) => entry.specifier);\n}\n","import type { PluginModule } from \"./plugin-types.js\";\n\nexport type ServiceName = string;\nexport const DEFAULT_PLUGIN_NAMES: readonly ServiceName[] = [];\nexport const SERVICE_NAMES: readonly ServiceName[] = DEFAULT_PLUGIN_NAMES;\nexport const DEFAULT_PLUGIN_REGISTRY: Record<ServiceName, PluginModule> = {};\n","import { loadExternalPluginModule } from \"./external-plugin-adapter.js\";\nimport { getLockedPluginSpecifiers } from \"./plugin-lock.js\";\nimport {\n DEFAULT_PLUGIN_REGISTRY,\n DEFAULT_PLUGIN_NAMES,\n SERVICE_NAMES,\n type ServiceName,\n} from \"./default-plugin-catalog.js\";\nimport type { PluginModule } from \"./plugin-types.js\";\nexport { DEFAULT_PLUGIN_REGISTRY, DEFAULT_PLUGIN_NAMES, SERVICE_NAMES, type ServiceName };\nexport type { LoadedPlugin, LoadedService, PluginModule, ServiceEntry } from \"./plugin-types.js\";\n\nexport interface ResolvePluginModulesOptions {\n includeInstalled?: boolean;\n}\n\nexport const DEFAULT_TOKENS = {\n tokens: {\n test_token_admin: {\n login: \"admin\",\n scopes: [\"repo\", \"user\", \"admin:org\", \"admin:repo_hook\"],\n },\n test_token_user1: {\n login: \"octocat\",\n scopes: [\"repo\", \"user\"],\n },\n },\n};\n\nexport async function resolvePluginModules(\n pluginSpecifiers: string[] = [],\n options: ResolvePluginModulesOptions = {},\n): Promise<Record<string, PluginModule>> {\n const installedSpecifiers = options.includeInstalled ? getLockedPluginSpecifiers() : [];\n const allSpecifiers = [...installedSpecifiers, ...pluginSpecifiers];\n const results = await Promise.all(allSpecifiers.map(loadExternalPluginModule));\n\n const externalEntries: Record<string, PluginModule> = {};\n for (const pluginModule of results) {\n if (pluginModule.name in DEFAULT_PLUGIN_REGISTRY) {\n throw new Error(`Plugin \"${pluginModule.name}\" conflicts with default plugin \"${pluginModule.name}\"`);\n }\n if (pluginModule.name in externalEntries) {\n throw new Error(`Duplicate plugin name \"${pluginModule.name}\"`);\n }\n externalEntries[pluginModule.name] = pluginModule;\n }\n\n return { ...DEFAULT_PLUGIN_REGISTRY, ...externalEntries };\n}\n\nexport const resolveServiceEntries = resolvePluginModules;\n\nexport function getDefaultPluginNames(): string[] {\n return [...DEFAULT_PLUGIN_NAMES];\n}\n","export interface ResolveBaseUrlOptions {\n service: string;\n port: number;\n baseUrl?: string;\n seedBaseUrl?: string;\n}\n\n/**\n * Fallback chain:\n * 1. Per-service baseUrl from seed config\n * 2. Explicit baseUrl (CLI flag or programmatic option)\n * 3. API_EMULATOR_BASE_URL env var (with {service} interpolation)\n * 4. EMULATE_BASE_URL env var for backward compatibility (with {service} interpolation)\n * 5. PORTLESS_URL env var (with {service} interpolation)\n * 6. http://localhost:<port>\n */\nexport function resolveBaseUrl(opts: ResolveBaseUrlOptions): string {\n if (opts.seedBaseUrl) {\n return opts.seedBaseUrl.replace(/\\{service\\}/g, opts.service);\n }\n if (opts.baseUrl) {\n return opts.baseUrl.replace(/\\{service\\}/g, opts.service);\n }\n const envBaseUrl = process.env.API_EMULATOR_BASE_URL ?? process.env.EMULATE_BASE_URL;\n if (envBaseUrl) {\n return envBaseUrl.replace(/\\{service\\}/g, opts.service);\n }\n const portlessUrl = process.env.PORTLESS_URL;\n if (portlessUrl) {\n return portlessUrl.replace(/\\{service\\}/g, opts.service);\n }\n return `http://localhost:${opts.port}`;\n}\n","import {\n createServer,\n createStoreFixture,\n fixtureStoreSnapshot,\n type AppKeyResolver,\n type FixtureInteraction,\n type FixtureSource,\n type Store,\n type StoreFixture,\n type StoreFixtureOptions,\n type StoreSnapshot,\n} from \"@api-emulator/core\";\nimport { serve } from \"@hono/node-server\";\nimport type { LoadedPlugin, PluginModule } from \"./registry.js\";\n\nexport interface SeedConfig {\n tokens?: Record<string, { login: string; scopes?: string[] }>;\n [service: string]: unknown;\n}\n\nexport type TokenMap = Record<string, { login: string; id: number; scopes?: string[] }>;\n\nexport interface ServiceRuntimeOptions {\n service: string;\n pluginModule: PluginModule;\n loadedPlugin: LoadedPlugin;\n port: number;\n baseUrl: string;\n tokens: TokenMap;\n seedConfig?: Record<string, unknown>;\n}\n\nexport interface RunningService {\n service: string;\n url: string;\n store: Store;\n snapshot(): StoreSnapshot;\n restore(fixture: FixtureSource): void;\n exportFixture(options?: StoreFixtureOptions): StoreFixture;\n resetToFixture(fixture: FixtureSource): void;\n reset(): void;\n close(): Promise<void>;\n}\n\nexport function createAuthTokens(seedConfig?: SeedConfig | null): TokenMap {\n const tokens: TokenMap = {};\n if (seedConfig?.tokens) {\n let tokenId = 100;\n for (const [token, user] of Object.entries(seedConfig.tokens)) {\n tokens[token] = { login: user.login, id: tokenId++, scopes: user.scopes };\n }\n } else {\n tokens[\"test_token_admin\"] = { login: \"admin\", id: 2, scopes: [\"repo\", \"user\", \"admin:org\", \"admin:repo_hook\"] };\n }\n return tokens;\n}\n\nexport function createServiceRuntime(options: ServiceRuntimeOptions): RunningService {\n const { service, pluginModule, loadedPlugin, port, baseUrl, tokens, seedConfig } = options;\n\n const resolverRef: { current?: AppKeyResolver } = {};\n const appKeyResolver: AppKeyResolver | undefined = loadedPlugin.createAppKeyResolver\n ? (appId) => resolverRef.current!(appId)\n : undefined;\n const fallbackUser = pluginModule.defaultFallback(seedConfig);\n\n const { app, store, webhooks } = createServer(loadedPlugin.plugin, {\n port,\n baseUrl,\n tokens,\n appKeyResolver,\n fallbackUser,\n });\n resolverRef.current = loadedPlugin.createAppKeyResolver?.(store);\n\n const seed = () => {\n loadedPlugin.plugin.seed?.(store, baseUrl);\n if (seedConfig && loadedPlugin.seedFromConfig) {\n loadedPlugin.seedFromConfig(store, baseUrl, seedConfig, webhooks);\n }\n };\n seed();\n\n const httpServer = serve({ fetch: app.fetch, port });\n\n return {\n service,\n url: baseUrl,\n store,\n snapshot() {\n return store.snapshot();\n },\n restore(fixture) {\n store.restore(fixtureStoreSnapshot(fixture));\n },\n exportFixture(options = {}) {\n const interactions = store.getData<FixtureInteraction[]>(\"api-emulator:interactions\");\n return createStoreFixture(service, store.snapshot(), {\n ...options,\n interactions: options.interactions ?? interactions,\n });\n },\n resetToFixture(fixture) {\n store.reset();\n store.restore(fixtureStoreSnapshot(fixture));\n },\n reset() {\n store.reset();\n seed();\n },\n close(): Promise<void> {\n return new Promise((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n };\n}\n","import { resolvePluginModules } from \"./registry.js\";\nexport type { ServiceName } from \"./registry.js\";\nimport type { ServiceName } from \"./registry.js\";\nimport type { FixtureSource, StoreFixture, StoreFixtureOptions, StoreSnapshot } from \"@api-emulator/core\";\nimport { resolveBaseUrl } from \"./base-url.js\";\nimport { createAuthTokens, createServiceRuntime, type SeedConfig } from \"./service-runtime.js\";\n\nexport type { SeedConfig };\nexport type {\n FixtureInteraction,\n FixtureSource,\n StoreFixture,\n StoreFixtureOptions,\n StoreSnapshot,\n} from \"@api-emulator/core\";\n\nexport interface EmulatorOptions {\n service: ServiceName | (string & {});\n port?: number;\n seed?: SeedConfig;\n baseUrl?: string;\n plugins?: string[];\n}\n\nexport interface Emulator {\n url: string;\n snapshot(): StoreSnapshot;\n restore(fixture: FixtureSource): void;\n exportFixture(options?: StoreFixtureOptions): StoreFixture;\n resetToFixture(fixture: FixtureSource): void;\n reset(): void;\n close(): Promise<void>;\n}\n\nexport async function createEmulator(options: EmulatorOptions): Promise<Emulator> {\n const { service, port = 4000, seed: seedConfig, plugins = [] } = options;\n\n const registry = await resolvePluginModules(plugins);\n const pluginModule = registry[service];\n if (!pluginModule) {\n throw new Error(`Unknown service: ${service}`);\n }\n\n const loadedPlugin = await pluginModule.load();\n\n const svcSeedConfig = seedConfig?.[service] as Record<string, unknown> | undefined;\n const seedBaseUrl =\n typeof svcSeedConfig?.baseUrl === \"string\" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : undefined;\n const baseUrl = resolveBaseUrl({ service, port, baseUrl: options.baseUrl, seedBaseUrl });\n const running = createServiceRuntime({\n service,\n pluginModule,\n loadedPlugin,\n port,\n baseUrl,\n tokens: createAuthTokens(seedConfig),\n seedConfig: svcSeedConfig,\n });\n\n return {\n url: running.url,\n snapshot: running.snapshot,\n restore: running.restore,\n exportFixture: running.exportFixture,\n resetToFixture: running.resetToFixture,\n reset: running.reset,\n close: running.close,\n };\n}\n"],"mappings":"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","names":["resolve","options","resolve"]}
1
+ {"version":3,"sources":["../src/external-plugin-adapter.ts","../src/plugin-manifest.ts","../src/plugin-lock.ts","../src/default-plugin-catalog.ts","../src/registry.ts","../src/base-url.ts","../src/service-runtime.ts","../src/api.ts"],"sourcesContent":["import type { ServicePlugin, Store, AuthFallback, WebhookDispatcher } from \"@api-emulator/core\";\nimport { isAbsolute, resolve } from \"path\";\nimport {\n formatPluginFidelity,\n readPluginManifest,\n validatePluginManifest,\n type PluginManifest,\n} from \"./plugin-manifest.js\";\nimport type { PluginModule } from \"./plugin-types.js\";\n\nexport interface ExternalPluginModule {\n plugin?: ServicePlugin;\n default?: ServicePlugin;\n seedFromConfig?(store: Store, baseUrl: string, config: unknown, webhooks?: WebhookDispatcher): void;\n manifest?: PluginManifest;\n defaultFallback?(svcSeedConfig?: Record<string, unknown>): AuthFallback;\n}\n\nexport async function loadExternalPluginModule(specifier: string): Promise<PluginModule> {\n const modulePath = specifier.startsWith(\".\") || isAbsolute(specifier) ? resolve(specifier) : specifier;\n\n const mod = (await import(modulePath)) as ExternalPluginModule;\n const plugin = mod.plugin ?? mod.default;\n if (!plugin || typeof plugin.register !== \"function\" || typeof plugin.name !== \"string\") {\n throw new Error(`Plugin \"${specifier}\" must export a ServicePlugin (as \"plugin\" or default export)`);\n }\n\n const name = plugin.name;\n const manifest = validatePluginManifest(readPluginManifest(mod), name);\n return {\n name,\n label: manifest.label,\n endpoints: manifest.endpoints,\n fidelity: formatPluginFidelity(manifest.fidelity),\n manifest,\n async load() {\n return {\n plugin,\n seedFromConfig: mod.seedFromConfig,\n };\n },\n defaultFallback: mod.defaultFallback ?? (() => ({ login: \"admin\", id: 1, scopes: [] })),\n initConfig: manifest.initConfig,\n };\n}\n\nexport async function loadExternalPlugin(specifier: string): Promise<{ name: string; entry: PluginModule }> {\n const pluginModule = await loadExternalPluginModule(specifier);\n return { name: pluginModule.name, entry: pluginModule };\n}\n","export interface PluginManifest {\n name?: string;\n label?: string;\n endpoints?: string;\n fidelity?: string | PluginFidelity;\n initConfig?: Record<string, unknown>;\n contract?: unknown;\n}\n\nexport interface PluginFidelity {\n level: string;\n endpoints?: string[];\n seedableResources?: string[];\n smoke?: string;\n notes?: string;\n}\n\nexport function formatPluginFidelity(fidelity: PluginManifest[\"fidelity\"]): string {\n if (!fidelity) return \"unrated\";\n if (typeof fidelity === \"string\") return fidelity;\n return fidelity.level;\n}\n\nexport function readPluginManifest(mod: { manifest?: PluginManifest }): PluginManifest {\n return mod.manifest ?? {};\n}\n\nexport function validatePluginManifest(\n manifest: PluginManifest,\n pluginName: string,\n): Required<Pick<PluginManifest, \"label\" | \"endpoints\" | \"initConfig\">> &\n Omit<PluginManifest, \"label\" | \"endpoints\" | \"initConfig\"> {\n if (manifest.name && manifest.name !== pluginName) {\n throw new Error(`Plugin manifest name \"${manifest.name}\" does not match plugin name \"${pluginName}\"`);\n }\n\n return {\n ...manifest,\n label: manifest.label ?? `${pluginName} (external plugin)`,\n endpoints: manifest.endpoints ?? \"\",\n initConfig: manifest.initConfig ?? {},\n };\n}\n","import { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { resolve } from \"path\";\n\nexport const PLUGIN_LOCK_FILE = \"api-emulator.lock\";\n\nexport interface PluginLockEntry {\n name: string;\n source: \"registry\" | \"specifier\";\n specifier: string;\n sourceId?: string;\n packageName?: string;\n version?: string;\n}\n\nexport interface PluginLock {\n version: 1;\n plugins: Record<string, PluginLockEntry>;\n}\n\nexport function createEmptyPluginLock(): PluginLock {\n return { version: 1, plugins: {} };\n}\n\nexport function readPluginLock(cwd = process.cwd()): PluginLock {\n const path = resolve(cwd, PLUGIN_LOCK_FILE);\n if (!existsSync(path)) return createEmptyPluginLock();\n\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as PluginLock;\n if (parsed.version !== 1 || typeof parsed.plugins !== \"object\" || parsed.plugins === null) {\n throw new Error(`Invalid ${PLUGIN_LOCK_FILE}`);\n }\n return parsed;\n}\n\nexport function writePluginLock(lock: PluginLock, cwd = process.cwd()): void {\n const sortedPlugins = Object.fromEntries(Object.entries(lock.plugins).sort(([a], [b]) => a.localeCompare(b)));\n const content = `${JSON.stringify({ version: 1, plugins: sortedPlugins }, null, 2)}\\n`;\n writeFileSync(resolve(cwd, PLUGIN_LOCK_FILE), content, \"utf-8\");\n}\n\nexport function getLockedPluginSpecifiers(cwd = process.cwd()): string[] {\n return Object.values(readPluginLock(cwd).plugins).map((entry) => entry.specifier);\n}\n","import type { PluginModule } from \"./plugin-types.js\";\n\nexport type ServiceName = string;\nexport const DEFAULT_PLUGIN_NAMES: readonly ServiceName[] = [];\nexport const SERVICE_NAMES: readonly ServiceName[] = DEFAULT_PLUGIN_NAMES;\nexport const DEFAULT_PLUGIN_REGISTRY: Record<ServiceName, PluginModule> = {};\n","import { loadExternalPluginModule } from \"./external-plugin-adapter.js\";\nimport { getLockedPluginSpecifiers } from \"./plugin-lock.js\";\nimport {\n DEFAULT_PLUGIN_REGISTRY,\n DEFAULT_PLUGIN_NAMES,\n SERVICE_NAMES,\n type ServiceName,\n} from \"./default-plugin-catalog.js\";\nimport type { PluginModule } from \"./plugin-types.js\";\nexport { DEFAULT_PLUGIN_REGISTRY, DEFAULT_PLUGIN_NAMES, SERVICE_NAMES, type ServiceName };\nexport type { LoadedPlugin, LoadedService, PluginModule, ServiceEntry } from \"./plugin-types.js\";\n\nexport interface ResolvePluginModulesOptions {\n includeInstalled?: boolean;\n}\n\nexport const DEFAULT_TOKENS = {\n tokens: {\n test_token_admin: {\n login: \"admin\",\n scopes: [\"repo\", \"user\", \"admin:org\", \"admin:repo_hook\"],\n },\n test_token_user1: {\n login: \"octocat\",\n scopes: [\"repo\", \"user\"],\n },\n },\n};\n\nexport async function resolvePluginModules(\n pluginSpecifiers: string[] = [],\n options: ResolvePluginModulesOptions = {},\n): Promise<Record<string, PluginModule>> {\n const installedSpecifiers = options.includeInstalled ? getLockedPluginSpecifiers() : [];\n const allSpecifiers = [...installedSpecifiers, ...pluginSpecifiers];\n const results = await Promise.all(allSpecifiers.map(loadExternalPluginModule));\n\n const externalEntries: Record<string, PluginModule> = {};\n for (const pluginModule of results) {\n if (pluginModule.name in DEFAULT_PLUGIN_REGISTRY) {\n throw new Error(`Plugin \"${pluginModule.name}\" conflicts with default plugin \"${pluginModule.name}\"`);\n }\n if (pluginModule.name in externalEntries) {\n throw new Error(`Duplicate plugin name \"${pluginModule.name}\"`);\n }\n externalEntries[pluginModule.name] = pluginModule;\n }\n\n return { ...DEFAULT_PLUGIN_REGISTRY, ...externalEntries };\n}\n\nexport const resolveServiceEntries = resolvePluginModules;\n\nexport function getDefaultPluginNames(): string[] {\n return [...DEFAULT_PLUGIN_NAMES];\n}\n","export interface ResolveBaseUrlOptions {\n service: string;\n port: number;\n baseUrl?: string;\n seedBaseUrl?: string;\n}\n\n/**\n * Fallback chain:\n * 1. Per-service baseUrl from seed config\n * 2. Explicit baseUrl (CLI flag or programmatic option)\n * 3. API_EMULATOR_BASE_URL env var (with {service} interpolation)\n * 4. PORTLESS_URL env var (with {service} interpolation)\n * 5. http://localhost:<port>\n */\nexport function resolveBaseUrl(opts: ResolveBaseUrlOptions): string {\n if (opts.seedBaseUrl) {\n return opts.seedBaseUrl.replace(/\\{service\\}/g, opts.service);\n }\n if (opts.baseUrl) {\n return opts.baseUrl.replace(/\\{service\\}/g, opts.service);\n }\n const envBaseUrl = process.env.API_EMULATOR_BASE_URL;\n if (envBaseUrl) {\n return envBaseUrl.replace(/\\{service\\}/g, opts.service);\n }\n const portlessUrl = process.env.PORTLESS_URL;\n if (portlessUrl) {\n return portlessUrl.replace(/\\{service\\}/g, opts.service);\n }\n return `http://localhost:${opts.port}`;\n}\n","import {\n createServer,\n createStoreFixture,\n fixtureStoreSnapshot,\n type AppKeyResolver,\n type FixtureInteraction,\n type FixtureSource,\n type Store,\n type StoreFixture,\n type StoreFixtureOptions,\n type StoreSnapshot,\n} from \"@api-emulator/core\";\nimport { serve } from \"@hono/node-server\";\nimport type { LoadedPlugin, PluginModule } from \"./registry.js\";\n\nexport interface SeedConfig {\n tokens?: Record<string, { login: string; scopes?: string[] }>;\n [service: string]: unknown;\n}\n\nexport type TokenMap = Record<string, { login: string; id: number; scopes?: string[] }>;\n\nexport interface ServiceRuntimeOptions {\n service: string;\n pluginModule: PluginModule;\n loadedPlugin: LoadedPlugin;\n port: number;\n baseUrl: string;\n tokens: TokenMap;\n seedConfig?: Record<string, unknown>;\n}\n\nexport interface RunningService {\n service: string;\n url: string;\n store: Store;\n snapshot(): StoreSnapshot;\n restore(fixture: FixtureSource): void;\n exportFixture(options?: StoreFixtureOptions): StoreFixture;\n resetToFixture(fixture: FixtureSource): void;\n reset(): void;\n close(): Promise<void>;\n}\n\nexport function createAuthTokens(seedConfig?: SeedConfig | null): TokenMap {\n const tokens: TokenMap = {};\n if (seedConfig?.tokens) {\n let tokenId = 100;\n for (const [token, user] of Object.entries(seedConfig.tokens)) {\n tokens[token] = { login: user.login, id: tokenId++, scopes: user.scopes };\n }\n } else {\n tokens[\"test_token_admin\"] = { login: \"admin\", id: 2, scopes: [\"repo\", \"user\", \"admin:org\", \"admin:repo_hook\"] };\n }\n return tokens;\n}\n\nexport function createServiceRuntime(options: ServiceRuntimeOptions): RunningService {\n const { service, pluginModule, loadedPlugin, port, baseUrl, tokens, seedConfig } = options;\n\n const resolverRef: { current?: AppKeyResolver } = {};\n const appKeyResolver: AppKeyResolver | undefined = loadedPlugin.createAppKeyResolver\n ? (appId) => resolverRef.current!(appId)\n : undefined;\n const fallbackUser = pluginModule.defaultFallback(seedConfig);\n\n const { app, store, webhooks } = createServer(loadedPlugin.plugin, {\n port,\n baseUrl,\n tokens,\n appKeyResolver,\n fallbackUser,\n });\n resolverRef.current = loadedPlugin.createAppKeyResolver?.(store);\n\n const seed = () => {\n loadedPlugin.plugin.seed?.(store, baseUrl);\n if (seedConfig && loadedPlugin.seedFromConfig) {\n loadedPlugin.seedFromConfig(store, baseUrl, seedConfig, webhooks);\n }\n };\n seed();\n\n const httpServer = serve({ fetch: app.fetch, port });\n\n return {\n service,\n url: baseUrl,\n store,\n snapshot() {\n return store.snapshot();\n },\n restore(fixture) {\n store.restore(fixtureStoreSnapshot(fixture));\n },\n exportFixture(options = {}) {\n const interactions = store.getData<FixtureInteraction[]>(\"api-emulator:interactions\");\n return createStoreFixture(service, store.snapshot(), {\n ...options,\n interactions: options.interactions ?? interactions,\n });\n },\n resetToFixture(fixture) {\n store.reset();\n store.restore(fixtureStoreSnapshot(fixture));\n },\n reset() {\n store.reset();\n seed();\n },\n close(): Promise<void> {\n return new Promise((resolve, reject) => {\n httpServer.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n };\n}\n","import { resolvePluginModules } from \"./registry.js\";\nexport type { ServiceName } from \"./registry.js\";\nimport type { ServiceName } from \"./registry.js\";\nimport type { FixtureSource, StoreFixture, StoreFixtureOptions, StoreSnapshot } from \"@api-emulator/core\";\nimport { resolveBaseUrl } from \"./base-url.js\";\nimport { createAuthTokens, createServiceRuntime, type SeedConfig } from \"./service-runtime.js\";\n\nexport type { SeedConfig };\nexport type {\n FixtureInteraction,\n FixtureSource,\n StoreFixture,\n StoreFixtureOptions,\n StoreSnapshot,\n} from \"@api-emulator/core\";\n\nexport interface EmulatorOptions {\n service: ServiceName | (string & {});\n port?: number;\n seed?: SeedConfig;\n baseUrl?: string;\n plugins?: string[];\n}\n\nexport interface Emulator {\n url: string;\n snapshot(): StoreSnapshot;\n restore(fixture: FixtureSource): void;\n exportFixture(options?: StoreFixtureOptions): StoreFixture;\n resetToFixture(fixture: FixtureSource): void;\n reset(): void;\n close(): Promise<void>;\n}\n\nexport async function createEmulator(options: EmulatorOptions): Promise<Emulator> {\n const { service, port = 4000, seed: seedConfig, plugins = [] } = options;\n\n const registry = await resolvePluginModules(plugins);\n const pluginModule = registry[service];\n if (!pluginModule) {\n throw new Error(`Unknown service: ${service}`);\n }\n\n const loadedPlugin = await pluginModule.load();\n\n const svcSeedConfig = seedConfig?.[service] as Record<string, unknown> | undefined;\n const seedBaseUrl =\n typeof svcSeedConfig?.baseUrl === \"string\" && svcSeedConfig.baseUrl.length > 0 ? svcSeedConfig.baseUrl : undefined;\n const baseUrl = resolveBaseUrl({ service, port, baseUrl: options.baseUrl, seedBaseUrl });\n const running = createServiceRuntime({\n service,\n pluginModule,\n loadedPlugin,\n port,\n baseUrl,\n tokens: createAuthTokens(seedConfig),\n seedConfig: svcSeedConfig,\n });\n\n return {\n url: running.url,\n snapshot: running.snapshot,\n restore: running.restore,\n exportFixture: running.exportFixture,\n resetToFixture: running.resetToFixture,\n reset: running.reset,\n close: running.close,\n };\n}\n"],"mappings":"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","names":["resolve","options","resolve"]}