@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/AUTHORING.md +330 -8
  2. package/CHANGELOG.md +85 -1
  3. package/README.md +64 -17
  4. package/SUBMISSION.md +86 -0
  5. package/bin/apifuse-check.ts +60 -6
  6. package/bin/apifuse-dev.ts +58 -8
  7. package/bin/apifuse-pack-check.ts +32 -2
  8. package/bin/apifuse-pack-smoke.ts +133 -6
  9. package/bin/apifuse-perf.ts +142 -49
  10. package/bin/apifuse-record.ts +182 -104
  11. package/bin/apifuse-submit-check.ts +2849 -0
  12. package/bin/apifuse.ts +1 -1
  13. package/dist/auth.d.ts +76 -0
  14. package/dist/auth.js +436 -0
  15. package/dist/ceremonies/index.d.ts +41 -0
  16. package/dist/ceremonies/index.js +490 -0
  17. package/dist/choice-token.d.ts +24 -0
  18. package/dist/choice-token.js +74 -0
  19. package/dist/cli/commands.d.ts +10 -0
  20. package/dist/cli/commands.js +80 -0
  21. package/dist/cli/create.d.ts +47 -0
  22. package/dist/cli/create.js +745 -0
  23. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  24. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  25. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  26. package/dist/cli/templates/provider/README.md.tpl +163 -0
  27. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  28. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  29. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  30. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  31. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  32. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  33. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  34. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  35. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  36. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  37. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  38. package/dist/config/loader.d.ts +107 -0
  39. package/dist/config/loader.js +935 -0
  40. package/dist/contract-json.d.ts +9 -0
  41. package/dist/contract-json.js +51 -0
  42. package/dist/contract-serialization.d.ts +4 -0
  43. package/dist/contract-serialization.js +78 -0
  44. package/dist/contract-types.d.ts +49 -0
  45. package/dist/contract-types.js +1 -0
  46. package/dist/contract.d.ts +6 -0
  47. package/dist/contract.js +156 -0
  48. package/dist/define.d.ts +100 -0
  49. package/dist/define.js +1383 -0
  50. package/dist/dev.d.ts +9 -0
  51. package/dist/dev.js +15 -0
  52. package/dist/errors.d.ts +59 -0
  53. package/dist/errors.js +97 -0
  54. package/dist/i18n/catalog.d.ts +29 -0
  55. package/dist/i18n/catalog.js +159 -0
  56. package/dist/i18n/index.d.ts +2 -0
  57. package/dist/i18n/index.js +2 -0
  58. package/dist/i18n/keys.d.ts +10 -0
  59. package/dist/i18n/keys.js +34 -0
  60. package/dist/index.d.ts +42 -0
  61. package/dist/index.js +38 -0
  62. package/dist/lint.d.ts +74 -0
  63. package/dist/lint.js +729 -0
  64. package/dist/observability.d.ts +5 -0
  65. package/dist/observability.js +39 -0
  66. package/dist/provider.d.ts +11 -0
  67. package/dist/provider.js +9 -0
  68. package/dist/public-schema-field-lint.d.ts +2 -0
  69. package/dist/public-schema-field-lint.js +158 -0
  70. package/dist/recipes/gov-api.d.ts +19 -0
  71. package/dist/recipes/gov-api.js +72 -0
  72. package/dist/recipes/rest-api.d.ts +21 -0
  73. package/dist/recipes/rest-api.js +115 -0
  74. package/dist/runtime/auth-flow.d.ts +14 -0
  75. package/dist/runtime/auth-flow.js +46 -0
  76. package/dist/runtime/browser.d.ts +25 -0
  77. package/dist/runtime/browser.js +1237 -0
  78. package/dist/runtime/cache.d.ts +10 -0
  79. package/dist/runtime/cache.js +372 -0
  80. package/dist/runtime/choice.d.ts +15 -0
  81. package/dist/runtime/choice.js +435 -0
  82. package/dist/runtime/credential.d.ts +8 -0
  83. package/dist/runtime/credential.js +61 -0
  84. package/dist/runtime/env.d.ts +2 -0
  85. package/dist/runtime/env.js +10 -0
  86. package/dist/runtime/executor.d.ts +16 -0
  87. package/dist/runtime/executor.js +51 -0
  88. package/dist/runtime/http.d.ts +8 -0
  89. package/dist/runtime/http.js +726 -0
  90. package/dist/runtime/insights.d.ts +9 -0
  91. package/dist/runtime/insights.js +324 -0
  92. package/dist/runtime/instrumentation.d.ts +8 -0
  93. package/dist/runtime/instrumentation.js +269 -0
  94. package/dist/runtime/key-derivation.d.ts +24 -0
  95. package/dist/runtime/key-derivation.js +73 -0
  96. package/dist/runtime/keyring.d.ts +25 -0
  97. package/dist/runtime/keyring.js +93 -0
  98. package/dist/runtime/namespace.d.ts +9 -0
  99. package/dist/runtime/namespace.js +19 -0
  100. package/dist/runtime/otlp.d.ts +39 -0
  101. package/dist/runtime/otlp.js +103 -0
  102. package/dist/runtime/perf.d.ts +12 -0
  103. package/dist/runtime/perf.js +52 -0
  104. package/dist/runtime/prevalidate.d.ts +12 -0
  105. package/dist/runtime/prevalidate.js +173 -0
  106. package/dist/runtime/provider.d.ts +2 -0
  107. package/dist/runtime/provider.js +11 -0
  108. package/dist/runtime/proxy-errors.d.ts +21 -0
  109. package/dist/runtime/proxy-errors.js +83 -0
  110. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  111. package/dist/runtime/proxy-telemetry.js +174 -0
  112. package/dist/runtime/redis.d.ts +17 -0
  113. package/dist/runtime/redis.js +82 -0
  114. package/dist/runtime/request-options.d.ts +3 -0
  115. package/dist/runtime/request-options.js +42 -0
  116. package/dist/runtime/state.d.ts +17 -0
  117. package/dist/runtime/state.js +344 -0
  118. package/dist/runtime/stealth.d.ts +21 -0
  119. package/dist/runtime/stealth.js +980 -0
  120. package/dist/runtime/stt.d.ts +22 -0
  121. package/dist/runtime/stt.js +480 -0
  122. package/dist/runtime/trace.d.ts +26 -0
  123. package/dist/runtime/trace.js +142 -0
  124. package/dist/runtime/waterfall.d.ts +12 -0
  125. package/dist/runtime/waterfall.js +147 -0
  126. package/dist/schema.d.ts +74 -0
  127. package/dist/schema.js +243 -0
  128. package/dist/serve.d.ts +1 -0
  129. package/dist/serve.js +1 -0
  130. package/dist/server/index.d.ts +3 -0
  131. package/dist/server/index.js +2 -0
  132. package/dist/server/serve.d.ts +64 -0
  133. package/dist/server/serve.js +1118 -0
  134. package/dist/server/types.d.ts +136 -0
  135. package/dist/server/types.js +86 -0
  136. package/dist/stealth/profiles.d.ts +4 -0
  137. package/dist/stealth/profiles.js +259 -0
  138. package/dist/stream.d.ts +44 -0
  139. package/dist/stream.js +151 -0
  140. package/dist/testing/helpers.d.ts +23 -0
  141. package/dist/testing/helpers.js +95 -0
  142. package/dist/testing/index.d.ts +2 -0
  143. package/dist/testing/index.js +2 -0
  144. package/dist/testing/run.d.ts +34 -0
  145. package/dist/testing/run.js +307 -0
  146. package/dist/types.d.ts +1467 -0
  147. package/dist/types.js +61 -0
  148. package/dist/utils/date.d.ts +6 -0
  149. package/dist/utils/date.js +101 -0
  150. package/dist/utils/parse.d.ts +16 -0
  151. package/dist/utils/parse.js +51 -0
  152. package/dist/utils/text.d.ts +4 -0
  153. package/dist/utils/text.js +14 -0
  154. package/dist/utils/transform.d.ts +8 -0
  155. package/dist/utils/transform.js +48 -0
  156. package/package.json +56 -29
  157. package/src/auth.ts +786 -0
  158. package/src/ceremonies/index.ts +8 -2
  159. package/src/choice-token.ts +165 -0
  160. package/src/cli/commands.ts +34 -11
  161. package/src/cli/create.ts +222 -128
  162. package/src/cli/templates/provider/.dockerignore.tpl +22 -0
  163. package/src/cli/templates/provider/.gitignore.tpl +22 -0
  164. package/src/cli/templates/provider/README.md.tpl +87 -7
  165. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  166. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  167. package/src/cli/templates/provider/index.ts.tpl +5 -47
  168. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  169. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  170. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  171. package/src/cli/templates/provider/operations/ping.ts.tpl +24 -0
  172. package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  173. package/src/cli/templates/provider/start.ts.tpl +1 -1
  174. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  175. package/src/config/loader.ts +1224 -9
  176. package/src/contract-json.ts +75 -0
  177. package/src/contract-serialization.ts +89 -0
  178. package/src/contract-types.ts +52 -0
  179. package/src/contract.ts +216 -0
  180. package/src/define.ts +1820 -70
  181. package/src/errors.ts +27 -0
  182. package/src/i18n/catalog.ts +277 -0
  183. package/src/i18n/index.ts +2 -0
  184. package/src/i18n/keys.ts +64 -0
  185. package/src/index.ts +189 -9
  186. package/src/lint.ts +580 -73
  187. package/src/observability.ts +41 -0
  188. package/src/provider.ts +131 -4
  189. package/src/public-schema-field-lint.ts +237 -0
  190. package/src/runtime/auth-flow.ts +9 -0
  191. package/src/runtime/browser.ts +1054 -51
  192. package/src/runtime/cache.ts +528 -0
  193. package/src/runtime/choice.ts +760 -0
  194. package/src/runtime/executor.ts +32 -3
  195. package/src/runtime/http.ts +980 -195
  196. package/src/runtime/insights.ts +11 -11
  197. package/src/runtime/instrumentation.ts +12 -4
  198. package/src/runtime/key-derivation.ts +1 -1
  199. package/src/runtime/keyring.ts +4 -3
  200. package/src/runtime/proxy-errors.ts +132 -0
  201. package/src/runtime/proxy-telemetry.ts +253 -0
  202. package/src/runtime/redis.ts +116 -0
  203. package/src/runtime/request-options.ts +66 -0
  204. package/src/runtime/state.ts +563 -0
  205. package/src/runtime/stealth.ts +1336 -0
  206. package/src/runtime/stt.ts +629 -0
  207. package/src/runtime/trace.ts +1 -1
  208. package/src/schema.ts +363 -1
  209. package/src/server/serve.ts +1192 -75
  210. package/src/server/types.ts +37 -0
  211. package/src/stream.ts +210 -0
  212. package/src/testing/run.ts +40 -6
  213. package/src/types.ts +1283 -59
  214. package/src/runtime/tls.ts +0 -434
  215. package/src/types/playwright-stealth.d.ts +0 -9
package/src/cli/create.ts CHANGED
@@ -1,18 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { dirname, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
- import {
8
- cancel,
9
- intro,
10
- isCancel,
11
- note,
12
- outro,
13
- select,
14
- text,
15
- } from "@clack/prompts";
7
+ import { cancel, intro, isCancel, note, outro, select, text } from "@clack/prompts";
16
8
  import { z } from "zod";
17
9
 
18
10
  import packageJson from "../../package.json";
@@ -28,12 +20,7 @@ export const CATEGORY_OPTIONS = [
28
20
  "communication",
29
21
  "other",
30
22
  ] as const;
31
- export const AUTH_MODE_OPTIONS = [
32
- "none",
33
- "platform-managed",
34
- "credentials",
35
- "oauth2",
36
- ] as const;
23
+ export const AUTH_MODE_OPTIONS = ["none", "platform-managed", "credentials", "oauth2"] as const;
37
24
  export const RUNTIME_OPTIONS = ["standard", "browser"] as const;
38
25
  export const PRESET_OPTIONS = ["standalone", "monorepo"] as const;
39
26
 
@@ -95,17 +82,13 @@ export type ProviderCreatePlan = {
95
82
  workspaceRoot?: string;
96
83
  };
97
84
 
98
- const TEMPLATE_DIR = fileURLToPath(
99
- new URL("./templates/provider/", import.meta.url),
100
- );
85
+ const TEMPLATE_DIR = fileURLToPath(new URL("./templates/provider/", import.meta.url));
101
86
  const HELP_TEXT = `Usage: apifuse create <provider-name> [options]
102
87
  Examples:
103
88
  apifuse create my-provider
104
- apifuse create my-provider --preset monorepo
105
89
  apifuse create --config ./apifuse.create.json --json
106
90
 
107
91
  Options:
108
- --preset <standalone|monorepo>
109
92
  --config <path>
110
93
  --output-dir <path>
111
94
  --display-name <name>
@@ -115,7 +98,7 @@ Options:
115
98
  --yes
116
99
  --dry-run
117
100
  --json
118
- --sdk-specifier <specifier> # internal/testing override for standalone dependency resolution
101
+ --sdk-specifier <specifier> # internal/testing override for dependency resolution
119
102
  --help, -h`;
120
103
 
121
104
  export async function main() {
@@ -128,9 +111,7 @@ export async function main() {
128
111
  }
129
112
 
130
113
  const parsed = parseArgs(normalizedArgs);
131
- const config = parsed.configPath
132
- ? await loadConfig(parsed.configPath)
133
- : undefined;
114
+ const config = parsed.configPath ? await loadConfig(parsed.configPath) : undefined;
134
115
  const resolved = await resolveCreateOptions(parsed, config, process.cwd());
135
116
  const plan = await buildProviderCreatePlan(resolved, process.cwd());
136
117
 
@@ -197,18 +178,10 @@ function parseArgs(argv: string[]): ParsedArgs {
197
178
  parsed.displayName = ensureValue(flag, consumeValue());
198
179
  break;
199
180
  case "--category":
200
- parsed.category = parseEnum(
201
- "category",
202
- consumeValue(),
203
- CATEGORY_OPTIONS,
204
- );
181
+ parsed.category = parseEnum("category", consumeValue(), CATEGORY_OPTIONS);
205
182
  break;
206
183
  case "--auth-mode":
207
- parsed.authMode = parseEnum(
208
- "auth mode",
209
- consumeValue(),
210
- AUTH_MODE_OPTIONS,
211
- );
184
+ parsed.authMode = parseEnum("auth mode", consumeValue(), AUTH_MODE_OPTIONS);
212
185
  break;
213
186
  case "--runtime":
214
187
  parsed.runtime = parseEnum("runtime", consumeValue(), RUNTIME_OPTIONS);
@@ -255,9 +228,7 @@ function parseEnum<T extends readonly string[]>(
255
228
  const resolvedValue = ensureValue(`--${label}`, value);
256
229
  const matchedValue = options.find((option) => option === resolvedValue);
257
230
  if (!matchedValue) {
258
- throw new Error(
259
- `Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`,
260
- );
231
+ throw new Error(`Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`);
261
232
  }
262
233
  return matchedValue;
263
234
  }
@@ -273,39 +244,32 @@ async function resolveCreateOptions(
273
244
  config: CreateConfigFile | undefined,
274
245
  cwd: string,
275
246
  ): Promise<CreateResolvedOptions> {
276
- const detectedWorkspaceRoot = findWorkspaceRoot(cwd);
277
- const detectedPreset: CreatePreset = detectedWorkspaceRoot
278
- ? "monorepo"
279
- : "standalone";
247
+ const internalWorkspaceRoot = findApifuseInternalWorkspaceRoot(cwd);
280
248
 
281
249
  const partial: Partial<CreateResolvedOptions> = {
282
250
  name: parsed.name ?? config?.name,
283
- preset: parsed.preset ?? config?.preset ?? detectedPreset,
251
+ preset: parsed.preset ?? config?.preset ?? "standalone",
284
252
  outputDir: parsed.outputDir ?? config?.outputDir,
285
253
  displayName: parsed.displayName ?? config?.displayName,
286
254
  category: parsed.category ?? config?.category,
287
255
  authMode: parsed.authMode ?? config?.authMode,
288
256
  runtime: parsed.runtime ?? config?.runtime,
289
257
  sdkSpecifier:
290
- parsed.sdkSpecifier ??
291
- config?.sdkSpecifier ??
292
- process.env.APIFUSE_SDK_SPECIFIER,
258
+ parsed.sdkSpecifier ?? config?.sdkSpecifier ?? process.env.APIFUSE__SDK__SPECIFIER,
293
259
  dryRun: parsed.dryRun,
294
260
  json: parsed.json,
295
261
  yes: parsed.yes,
296
262
  };
297
263
 
298
- if (partial.preset === "monorepo" && !detectedWorkspaceRoot) {
264
+ if (partial.preset === "monorepo" && !internalWorkspaceRoot) {
299
265
  throw new Error(
300
- "Monorepo preset requires a workspace root with a providers/ directory.",
266
+ "Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
301
267
  );
302
268
  }
303
269
 
304
270
  if (partial.yes) {
305
271
  if (!partial.name) {
306
- throw new Error(
307
- "--yes requires a provider name (positional or via config).",
308
- );
272
+ throw new Error("--yes requires a provider name (positional or via config).");
309
273
  }
310
274
 
311
275
  return {
@@ -314,7 +278,7 @@ async function resolveCreateOptions(
314
278
  category: partial.category ?? "other",
315
279
  authMode: partial.authMode ?? "none",
316
280
  runtime: partial.runtime ?? "standard",
317
- preset: partial.preset ?? detectedPreset,
281
+ preset: partial.preset ?? "standalone",
318
282
  outputDir: partial.outputDir,
319
283
  dryRun: partial.dryRun ?? false,
320
284
  json: partial.json ?? false,
@@ -324,10 +288,10 @@ async function resolveCreateOptions(
324
288
  }
325
289
 
326
290
  if (!partial.json) {
327
- intro("Create a new ApiFuse provider");
291
+ intro("Create a new APIFuse provider");
328
292
  note(
329
- `Preset precedence: explicit flags > config file > workspace detection > standalone default\nDetected workspace preset: ${detectedPreset}`,
330
- "Preset resolution",
293
+ "External bounty workspaces are one-provider repositories. The public create flow defaults to standalone.",
294
+ "Provider workspace",
331
295
  );
332
296
  }
333
297
 
@@ -392,22 +356,7 @@ async function resolveCreateOptions(
392
356
  initialValue: "standard",
393
357
  }),
394
358
  )),
395
- preset:
396
- partial.preset ??
397
- (await promptValue(
398
- select({
399
- message: "Generation preset",
400
- options: PRESET_OPTIONS.map((value) => ({
401
- label: value,
402
- value,
403
- hint:
404
- value === "standalone"
405
- ? "Create a clean-room npm-ready provider package."
406
- : "Create the provider inside the current ApiFuse monorepo.",
407
- })),
408
- initialValue: detectedPreset,
409
- }),
410
- )),
359
+ preset: partial.preset ?? "standalone",
411
360
  outputDir: partial.outputDir,
412
361
  dryRun: partial.dryRun ?? false,
413
362
  json: partial.json ?? false,
@@ -429,24 +378,20 @@ export async function buildProviderCreatePlan(
429
378
  options: CreateResolvedOptions,
430
379
  cwd: string,
431
380
  ): Promise<ProviderCreatePlan> {
432
- const workspaceRoot =
433
- options.preset === "monorepo" ? findWorkspaceRoot(cwd) : undefined;
434
- if (options.preset === "monorepo" && !workspaceRoot) {
381
+ const resolvedWorkspaceRoot =
382
+ options.preset === "monorepo" ? findApifuseInternalWorkspaceRoot(cwd) : undefined;
383
+ if (options.preset === "monorepo" && !resolvedWorkspaceRoot) {
435
384
  throw new Error(
436
- "Monorepo preset requires a workspace root with a providers/ directory.",
385
+ "Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
437
386
  );
438
387
  }
439
- const resolvedWorkspaceRoot =
440
- options.preset === "monorepo" ? workspaceRoot : undefined;
441
388
  let providerRoot: string;
442
389
  let installCwd: string;
443
390
 
444
391
  if (options.outputDir) {
445
392
  providerRoot = resolve(cwd, options.outputDir);
446
393
  installCwd =
447
- options.preset === "monorepo" && resolvedWorkspaceRoot
448
- ? resolvedWorkspaceRoot
449
- : providerRoot;
394
+ options.preset === "monorepo" && resolvedWorkspaceRoot ? resolvedWorkspaceRoot : providerRoot;
450
395
  } else if (options.preset === "monorepo" && resolvedWorkspaceRoot) {
451
396
  providerRoot = resolve(resolvedWorkspaceRoot, "providers", options.name);
452
397
  installCwd = resolvedWorkspaceRoot;
@@ -459,33 +404,101 @@ export async function buildProviderCreatePlan(
459
404
  throw new Error(`Target directory already exists: ${providerRoot}`);
460
405
  }
461
406
 
407
+ if (options.sdkSpecifier?.startsWith("workspace:") && !resolvedWorkspaceRoot) {
408
+ throw new Error(
409
+ "workspace:* is only valid inside the APIFuse monorepo because public Provider SDK scaffolds must install from npm or an explicit tarball/file specifier.",
410
+ );
411
+ }
412
+
462
413
  const sdkSpecifier =
463
414
  options.sdkSpecifier ??
464
- (options.preset === "monorepo" ? "workspace:*" : `^${packageJson.version}`);
415
+ (options.preset === "monorepo" && resolvedWorkspaceRoot
416
+ ? "workspace:*"
417
+ : `^${packageJson.version}`);
465
418
  const relativeProviderRoot = relative(cwd, providerRoot) || options.name;
466
419
  const nextDevCommand = `cd ${relativeProviderRoot} && bun run dev`;
467
420
  const packageName =
468
421
  options.preset === "monorepo"
469
422
  ? `@apifuse/provider-${options.name}`
470
423
  : `apifuse-provider-${options.name}`;
424
+ const templateValues = {
425
+ PROVIDER_ID: options.name,
426
+ DISPLAY_NAME: escapeTemplate(options.displayName),
427
+ CATEGORY: options.category,
428
+ RUNTIME: options.runtime,
429
+ BROWSER_BLOCK:
430
+ options.runtime === "browser"
431
+ ? ',\n browser: {\n engine: "playwright-stealth",\n }'
432
+ : "",
433
+ SECRETS_BLOCK: renderSecretsBlock(options.authMode),
434
+ CREDENTIAL_BLOCK: renderCredentialBlock(options.authMode),
435
+ AUTH_BLOCK: renderAuthBlock(options.authMode),
436
+ };
471
437
 
472
438
  const files: ProviderPlanFile[] = [
439
+ {
440
+ path: resolve(providerRoot, ".dockerignore"),
441
+ content: await renderTemplate(".dockerignore.tpl", {}),
442
+ },
443
+ {
444
+ path: resolve(providerRoot, ".gitignore"),
445
+ content: await renderTemplate(".gitignore.tpl", {}),
446
+ },
473
447
  {
474
448
  path: resolve(providerRoot, "index.ts"),
475
- content: await renderTemplate("index.ts.tpl", {
449
+ content: await renderTemplate("index.ts.tpl", templateValues),
450
+ },
451
+ {
452
+ path: resolve(providerRoot, "meta.ts"),
453
+ content: await renderTemplate("meta.ts.tpl", {
476
454
  PROVIDER_ID: options.name,
477
455
  DISPLAY_NAME: escapeTemplate(options.displayName),
478
456
  CATEGORY: options.category,
479
- RUNTIME: options.runtime,
480
- BROWSER_BLOCK:
457
+ }),
458
+ },
459
+ {
460
+ path: resolve(providerRoot, "operations", "index.ts"),
461
+ content: await renderTemplate("operations/index.ts.tpl", {}),
462
+ },
463
+ {
464
+ path: resolve(providerRoot, "operations", "ping.ts"),
465
+ content: await renderTemplate("operations/ping.ts.tpl", {
466
+ DISPLAY_NAME: escapeTemplate(options.displayName),
467
+ HANDLER_CTX: options.runtime === "browser" ? "ctx" : "_ctx",
468
+ BROWSER_HANDLER_BLOCK:
469
+ options.runtime === "browser"
470
+ ? '\n const page = await ctx.browser.newPage();\n await page.goto("https://example.com");\n const title = await page.title();\n const frames = await page.frames();\n await page.close();\n'
471
+ : "",
472
+ BROWSER_RESPONSE_FIELDS:
481
473
  options.runtime === "browser"
482
- ? ',\n browser: {\n engine: "playwright-stealth",\n }'
474
+ ? ",\n pageTitle: title,\n frameCount: frames.length"
483
475
  : "",
484
- SECRETS_BLOCK: renderSecretsBlock(options.authMode),
485
- CREDENTIAL_BLOCK: renderCredentialBlock(options.authMode),
486
- AUTH_BLOCK: renderAuthBlock(options.authMode),
487
476
  }),
488
477
  },
478
+ {
479
+ path: resolve(providerRoot, "schemas", "ping.ts"),
480
+ content: await renderTemplate("schemas/ping.ts.tpl", {}),
481
+ },
482
+ {
483
+ path: resolve(providerRoot, "upstream", "README.md"),
484
+ content: await renderTemplate("upstream/README.md.tpl", {}),
485
+ },
486
+ {
487
+ path: resolve(providerRoot, "mappers", "README.md"),
488
+ content: await renderTemplate("mappers/README.md.tpl", {}),
489
+ },
490
+ {
491
+ path: resolve(providerRoot, "domain", "README.md"),
492
+ content: await renderTemplate("domain/README.md.tpl", {}),
493
+ },
494
+ {
495
+ path: resolve(providerRoot, "locales", "en.json"),
496
+ content: renderStarterLocaleCatalog(options.displayName, "en"),
497
+ },
498
+ {
499
+ path: resolve(providerRoot, "locales", "ko.json"),
500
+ content: renderStarterLocaleCatalog(options.displayName, "ko"),
501
+ },
489
502
  {
490
503
  path: resolve(providerRoot, "package.json"),
491
504
  content: renderPackageJson({
@@ -538,15 +551,17 @@ export async function buildProviderCreatePlan(
538
551
  packageName,
539
552
  preset: options.preset,
540
553
  providerRoot,
541
- validationCommands: ["bun run check", "bun run test"],
554
+ validationCommands: [
555
+ "bun run check",
556
+ "bun run type-check",
557
+ "bun run submit-check -- --smoke",
558
+ "bun run test",
559
+ ],
542
560
  workspaceRoot: resolvedWorkspaceRoot,
543
561
  };
544
562
  }
545
563
 
546
- async function renderTemplate(
547
- fileName: string,
548
- values: Record<string, string>,
549
- ): Promise<string> {
564
+ async function renderTemplate(fileName: string, values: Record<string, string>): Promise<string> {
550
565
  const templatePath = resolve(TEMPLATE_DIR, fileName);
551
566
  const template = await readFile(templatePath, "utf8");
552
567
  return template.replace(/\{\{([A-Z_]+)\}\}/g, (_match, key: string) => {
@@ -554,10 +569,7 @@ async function renderTemplate(
554
569
  });
555
570
  }
556
571
 
557
- function renderPackageJson(input: {
558
- packageName: string;
559
- sdkSpecifier: string;
560
- }): string {
572
+ function renderPackageJson(input: { packageName: string; sdkSpecifier: string }): string {
561
573
  return `${JSON.stringify(
562
574
  {
563
575
  name: input.packageName,
@@ -567,10 +579,11 @@ function renderPackageJson(input: {
567
579
  main: "./index.ts",
568
580
  scripts: {
569
581
  dev: "apifuse dev .",
570
- check: "apifuse check .",
582
+ check: "apifuse check . && bun run type-check",
583
+ "type-check": "tsc --noEmit",
584
+ "submit-check": "apifuse submit-check . --markdown submission-report.md",
571
585
  test: "apifuse test .",
572
586
  record: "apifuse record .",
573
- "perf:sample": "apifuse perf . --operation ping --runs 3",
574
587
  start: "bun start.ts",
575
588
  },
576
589
  dependencies: {
@@ -578,6 +591,7 @@ function renderPackageJson(input: {
578
591
  },
579
592
  devDependencies: {
580
593
  "@types/bun": "latest",
594
+ typescript: "^6.0.3",
581
595
  },
582
596
  },
583
597
  null,
@@ -596,6 +610,7 @@ function renderTsconfig(): string {
596
610
  noEmit: true,
597
611
  skipLibCheck: true,
598
612
  resolveJsonModule: true,
613
+ types: ["bun"],
599
614
  },
600
615
  include: ["**/*.ts"],
601
616
  exclude: ["node_modules"],
@@ -605,6 +620,61 @@ function renderTsconfig(): string {
605
620
  )}\n`;
606
621
  }
607
622
 
623
+ function renderStarterLocaleCatalog(displayName: string, locale: "en" | "ko"): string {
624
+ const catalog = {
625
+ meta: {
626
+ displayName,
627
+ description:
628
+ locale === "ko"
629
+ ? `${displayName} APIFuse 커뮤니티 기여용 provider starter입니다.`
630
+ : `${displayName} provider starter for APIFuse community contributions.`,
631
+ },
632
+ operations: {
633
+ ping: {
634
+ description:
635
+ locale === "ko"
636
+ ? "생성된 provider wiring이 APIFuse runtime contract를 통해 작은 샘플 payload를 정상적으로 round-trip하는지 확인합니다. 로컬 개발, baseline check, 첫 bounty scaffold 검증에 사용합니다. production data retrieval이나 upstream-specific workflow에는 사용하지 마세요. 이 starter operation은 생성된 프로젝트가 compile, serve, input/output round-trip을 수행하는지 증명하기 위한 용도입니다."
637
+ : "Confirms the generated provider wiring is operational by echoing a small sample payload through the APIFuse runtime contract. Use when validating local development, baseline checks, or first-pass bounty scaffolds. Do NOT use for production data retrieval or upstream-specific workflows because this starter operation exists only to prove the generated project compiles, serves, and round-trips input/output correctly.",
638
+ },
639
+ },
640
+ schemaDescriptions: {
641
+ input: {
642
+ root:
643
+ locale === "ko"
644
+ ? "생성된 ping operation의 입력 payload"
645
+ : "Input payload for the generated ping operation.",
646
+ value:
647
+ locale === "ko"
648
+ ? "생성된 provider scaffold wiring 검증에 사용하는 샘플 입력값"
649
+ : "Sample input value used to verify the generated provider scaffold is wired correctly.",
650
+ },
651
+ output: {
652
+ root:
653
+ locale === "ko"
654
+ ? "생성된 ping operation이 반환하는 출력 payload"
655
+ : "Output payload returned by the generated ping operation.",
656
+ ok:
657
+ locale === "ko"
658
+ ? "생성된 provider가 샘플 요청을 성공적으로 처리했는지 여부"
659
+ : "Whether the generated provider handled the sample request successfully.",
660
+ message:
661
+ locale === "ko"
662
+ ? "생성된 provider가 샘플 payload를 round-trip했음을 보여주는 사람이 읽을 수 있는 확인 메시지"
663
+ : "Human-readable confirmation that the generated provider round-tripped the sample payload.",
664
+ pageTitle:
665
+ locale === "ko"
666
+ ? "browser 런타임 provider일 때 로드된 페이지의 제목 (해당되지 않으면 생략)"
667
+ : "Title of the loaded page when the provider uses the browser runtime; omitted otherwise.",
668
+ frameCount:
669
+ locale === "ko"
670
+ ? "browser 런타임 provider일 때 로드된 페이지의 frame 개수 (해당되지 않으면 생략)"
671
+ : "Number of frames in the loaded page when the provider uses the browser runtime; omitted otherwise.",
672
+ },
673
+ },
674
+ };
675
+ return `${JSON.stringify(catalog, null, 2)}\n`;
676
+ }
677
+
608
678
  function renderAuthBlock(authMode: CreateAuthMode): string {
609
679
  switch (authMode) {
610
680
  case "none":
@@ -615,8 +685,8 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
615
685
  return `{
616
686
  mode: "credentials",
617
687
  flow: {
618
- start: async () => ({
619
- kind: "input",
688
+ start: async (_ctx) => ({
689
+ kind: "form",
620
690
  turnId: crypto.randomUUID(),
621
691
  expectedInput: {
622
692
  schema: {
@@ -642,13 +712,24 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
642
712
  },
643
713
  hint: "Generated placeholder credential flow completed. Replace this with real auth logic.",
644
714
  }),
715
+ refresh: async () => ({
716
+ kind: "complete",
717
+ turnId: crypto.randomUUID(),
718
+ data: {
719
+ credential: {
720
+ username: "replace-with-refreshed-username",
721
+ password: "replace-with-refreshed-password",
722
+ },
723
+ },
724
+ hint: "Return refreshed credential data here, or throw AuthError with code AUTH_REQUIRED when silent refresh is not possible.",
725
+ }),
645
726
  },
646
727
  }`;
647
728
  case "oauth2":
648
729
  return `{
649
730
  mode: "oauth2",
650
731
  flow: {
651
- start: async () => ({
732
+ start: async (_ctx) => ({
652
733
  kind: "redirect",
653
734
  turnId: crypto.randomUUID(),
654
735
  data: {
@@ -713,11 +794,11 @@ export function toDisplayName(name: string): string {
713
794
  .join(" ");
714
795
  }
715
796
 
716
- function findWorkspaceRoot(cwd: string): string | undefined {
797
+ function findApifuseInternalWorkspaceRoot(cwd: string): string | undefined {
717
798
  let currentDirectory = cwd;
718
799
 
719
800
  while (true) {
720
- if (existsSync(resolve(currentDirectory, "providers"))) {
801
+ if (isApifuseInternalWorkspaceRoot(currentDirectory)) {
721
802
  return currentDirectory;
722
803
  }
723
804
 
@@ -730,6 +811,29 @@ function findWorkspaceRoot(cwd: string): string | undefined {
730
811
  }
731
812
  }
732
813
 
814
+ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
815
+ const providerSdkPackageJsonPath = resolve(
816
+ workspaceRoot,
817
+ "packages",
818
+ "provider-sdk",
819
+ "package.json",
820
+ );
821
+ if (!existsSync(providerSdkPackageJsonPath)) {
822
+ return false;
823
+ }
824
+ try {
825
+ const packageJson = JSON.parse(readFileSync(providerSdkPackageJsonPath, "utf8"));
826
+ return (
827
+ typeof packageJson === "object" &&
828
+ packageJson !== null &&
829
+ "name" in packageJson &&
830
+ packageJson.name === "@apifuse/provider-sdk"
831
+ );
832
+ } catch {
833
+ return false;
834
+ }
835
+ }
836
+
733
837
  async function writePlan(plan: ProviderCreatePlan): Promise<void> {
734
838
  for (const file of plan.files) {
735
839
  await mkdir(dirname(file.path), { recursive: true });
@@ -737,27 +841,17 @@ async function writePlan(plan: ProviderCreatePlan): Promise<void> {
737
841
  }
738
842
  }
739
843
 
740
- async function installDependencies(
741
- plan: ProviderCreatePlan,
742
- jsonMode: boolean,
743
- ): Promise<void> {
844
+ async function installDependencies(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
744
845
  await runCommand(plan.installCommand, plan.installCwd, jsonMode);
745
846
  }
746
847
 
747
- async function runBaselineValidation(
748
- plan: ProviderCreatePlan,
749
- jsonMode: boolean,
750
- ): Promise<void> {
848
+ async function runBaselineValidation(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
751
849
  for (const command of plan.validationCommands) {
752
850
  await runCommand(command, plan.providerRoot, jsonMode);
753
851
  }
754
852
  }
755
853
 
756
- async function runCommand(
757
- command: string,
758
- cwd: string,
759
- jsonMode: boolean,
760
- ): Promise<void> {
854
+ async function runCommand(command: string, cwd: string, jsonMode: boolean): Promise<void> {
761
855
  const [binary, ...args] = command.split(" ");
762
856
  if (!binary) {
763
857
  throw new Error(`Cannot run empty command in ${cwd}`);
@@ -789,9 +883,7 @@ async function runCommand(
789
883
  rejectPromise(
790
884
  new Error(
791
885
  `Command failed (${command}) in ${cwd}${
792
- stdout || stderr
793
- ? `\n${[stdout, stderr].filter(Boolean).join("\n")}`
794
- : ""
886
+ stdout || stderr ? `\n${[stdout, stderr].filter(Boolean).join("\n")}` : ""
795
887
  }`,
796
888
  ),
797
889
  );
@@ -799,11 +891,7 @@ async function runCommand(
799
891
  });
800
892
  }
801
893
 
802
- function printResult(
803
- plan: ProviderCreatePlan,
804
- jsonMode: boolean,
805
- dryRun: boolean,
806
- ) {
894
+ function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolean) {
807
895
  const payload = {
808
896
  success: true,
809
897
  dryRun,
@@ -820,9 +908,7 @@ function printResult(
820
908
  },
821
909
  validationCommands: plan.validationCommands,
822
910
  nextDevCommand: plan.nextDevCommand,
823
- files: plan.files.map(
824
- (file) => relative(plan.providerRoot, file.path) || file.path,
825
- ),
911
+ files: plan.files.map((file) => relative(plan.providerRoot, file.path) || file.path),
826
912
  };
827
913
 
828
914
  if (jsonMode) {
@@ -837,6 +923,14 @@ function printResult(
837
923
  console.log(`Validation: (cd ${plan.providerRoot} && ${command})`);
838
924
  }
839
925
  console.log(`Next local dev: ${plan.nextDevCommand}`);
926
+ console.log(
927
+ "Submission evidence: run `bun run submit-check -- --smoke` to archive measured `/health` and `POST /v1/{operation}` results.",
928
+ );
929
+ if (plan.files.some((file) => file.content.includes('runtime: "browser"'))) {
930
+ console.log(
931
+ "Browser runtime: run `bunx playwright install chromium` locally or set `APIFUSE__CDP_POOL__URL` before browser-backed smoke tests.",
932
+ );
933
+ }
840
934
  }
841
935
 
842
936
  function escapeTemplate(value: string): string {
@@ -0,0 +1,22 @@
1
+ node_modules/
2
+ .git/
3
+ .github/
4
+
5
+ # Environment and local secrets
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+
10
+ # Local reports and generated artifacts
11
+ submission-report.md
12
+ coverage/
13
+ dist/
14
+ .cache/
15
+ .turbo/
16
+ .bun/
17
+ *.tsbuildinfo
18
+
19
+ # OS/editor junk
20
+ .DS_Store
21
+ Thumbs.db
22
+ *.swp
@@ -0,0 +1,22 @@
1
+ node_modules/
2
+
3
+ # Environment and local secrets
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+
8
+ # Build, coverage, cache, and local runtime artifacts
9
+ coverage/
10
+ dist/
11
+ .cache/
12
+ .turbo/
13
+ .bun/
14
+ *.tsbuildinfo
15
+
16
+ # Bounty submission output
17
+ submission-report.md
18
+
19
+ # OS/editor junk
20
+ .DS_Store
21
+ Thumbs.db
22
+ *.swp