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

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 (231) hide show
  1. package/AUTHORING.md +330 -8
  2. package/CHANGELOG.md +89 -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 +3243 -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 +777 -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/AGENTS.md.tpl +87 -0
  26. package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -0
  27. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  28. package/dist/cli/templates/provider/README.md.tpl +163 -0
  29. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  30. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  31. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  32. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  33. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  34. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  35. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  36. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  37. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  38. package/dist/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  39. package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  40. package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  41. package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  42. package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  43. package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  44. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  45. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  46. package/dist/config/loader.d.ts +107 -0
  47. package/dist/config/loader.js +935 -0
  48. package/dist/contract-json.d.ts +9 -0
  49. package/dist/contract-json.js +51 -0
  50. package/dist/contract-serialization.d.ts +4 -0
  51. package/dist/contract-serialization.js +78 -0
  52. package/dist/contract-types.d.ts +49 -0
  53. package/dist/contract-types.js +1 -0
  54. package/dist/contract.d.ts +6 -0
  55. package/dist/contract.js +156 -0
  56. package/dist/define.d.ts +100 -0
  57. package/dist/define.js +1383 -0
  58. package/dist/dev.d.ts +9 -0
  59. package/dist/dev.js +15 -0
  60. package/dist/errors.d.ts +59 -0
  61. package/dist/errors.js +97 -0
  62. package/dist/i18n/catalog.d.ts +29 -0
  63. package/dist/i18n/catalog.js +159 -0
  64. package/dist/i18n/index.d.ts +2 -0
  65. package/dist/i18n/index.js +2 -0
  66. package/dist/i18n/keys.d.ts +10 -0
  67. package/dist/i18n/keys.js +34 -0
  68. package/dist/index.d.ts +42 -0
  69. package/dist/index.js +38 -0
  70. package/dist/lint.d.ts +74 -0
  71. package/dist/lint.js +729 -0
  72. package/dist/observability.d.ts +5 -0
  73. package/dist/observability.js +39 -0
  74. package/dist/provider.d.ts +11 -0
  75. package/dist/provider.js +9 -0
  76. package/dist/public-schema-field-lint.d.ts +2 -0
  77. package/dist/public-schema-field-lint.js +158 -0
  78. package/dist/recipes/gov-api.d.ts +19 -0
  79. package/dist/recipes/gov-api.js +72 -0
  80. package/dist/recipes/rest-api.d.ts +21 -0
  81. package/dist/recipes/rest-api.js +115 -0
  82. package/dist/runtime/auth-flow.d.ts +14 -0
  83. package/dist/runtime/auth-flow.js +46 -0
  84. package/dist/runtime/browser.d.ts +25 -0
  85. package/dist/runtime/browser.js +1237 -0
  86. package/dist/runtime/cache.d.ts +10 -0
  87. package/dist/runtime/cache.js +372 -0
  88. package/dist/runtime/choice.d.ts +15 -0
  89. package/dist/runtime/choice.js +435 -0
  90. package/dist/runtime/credential.d.ts +8 -0
  91. package/dist/runtime/credential.js +61 -0
  92. package/dist/runtime/env.d.ts +2 -0
  93. package/dist/runtime/env.js +10 -0
  94. package/dist/runtime/executor.d.ts +16 -0
  95. package/dist/runtime/executor.js +51 -0
  96. package/dist/runtime/http.d.ts +8 -0
  97. package/dist/runtime/http.js +726 -0
  98. package/dist/runtime/insights.d.ts +9 -0
  99. package/dist/runtime/insights.js +324 -0
  100. package/dist/runtime/instrumentation.d.ts +8 -0
  101. package/dist/runtime/instrumentation.js +269 -0
  102. package/dist/runtime/key-derivation.d.ts +24 -0
  103. package/dist/runtime/key-derivation.js +73 -0
  104. package/dist/runtime/keyring.d.ts +25 -0
  105. package/dist/runtime/keyring.js +93 -0
  106. package/dist/runtime/namespace.d.ts +9 -0
  107. package/dist/runtime/namespace.js +19 -0
  108. package/dist/runtime/otlp.d.ts +39 -0
  109. package/dist/runtime/otlp.js +103 -0
  110. package/dist/runtime/perf.d.ts +12 -0
  111. package/dist/runtime/perf.js +52 -0
  112. package/dist/runtime/prevalidate.d.ts +12 -0
  113. package/dist/runtime/prevalidate.js +173 -0
  114. package/dist/runtime/provider.d.ts +2 -0
  115. package/dist/runtime/provider.js +11 -0
  116. package/dist/runtime/proxy-errors.d.ts +21 -0
  117. package/dist/runtime/proxy-errors.js +83 -0
  118. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  119. package/dist/runtime/proxy-telemetry.js +174 -0
  120. package/dist/runtime/redis.d.ts +17 -0
  121. package/dist/runtime/redis.js +82 -0
  122. package/dist/runtime/request-options.d.ts +3 -0
  123. package/dist/runtime/request-options.js +42 -0
  124. package/dist/runtime/state.d.ts +17 -0
  125. package/dist/runtime/state.js +344 -0
  126. package/dist/runtime/stealth.d.ts +21 -0
  127. package/dist/runtime/stealth.js +980 -0
  128. package/dist/runtime/stt.d.ts +22 -0
  129. package/dist/runtime/stt.js +480 -0
  130. package/dist/runtime/trace.d.ts +26 -0
  131. package/dist/runtime/trace.js +142 -0
  132. package/dist/runtime/waterfall.d.ts +12 -0
  133. package/dist/runtime/waterfall.js +147 -0
  134. package/dist/schema.d.ts +74 -0
  135. package/dist/schema.js +243 -0
  136. package/dist/serve.d.ts +1 -0
  137. package/dist/serve.js +1 -0
  138. package/dist/server/index.d.ts +3 -0
  139. package/dist/server/index.js +2 -0
  140. package/dist/server/serve.d.ts +64 -0
  141. package/dist/server/serve.js +1118 -0
  142. package/dist/server/types.d.ts +136 -0
  143. package/dist/server/types.js +86 -0
  144. package/dist/stealth/profiles.d.ts +4 -0
  145. package/dist/stealth/profiles.js +259 -0
  146. package/dist/stream.d.ts +44 -0
  147. package/dist/stream.js +151 -0
  148. package/dist/testing/helpers.d.ts +23 -0
  149. package/dist/testing/helpers.js +95 -0
  150. package/dist/testing/index.d.ts +2 -0
  151. package/dist/testing/index.js +2 -0
  152. package/dist/testing/run.d.ts +34 -0
  153. package/dist/testing/run.js +307 -0
  154. package/dist/types.d.ts +1467 -0
  155. package/dist/types.js +61 -0
  156. package/dist/utils/date.d.ts +6 -0
  157. package/dist/utils/date.js +101 -0
  158. package/dist/utils/parse.d.ts +16 -0
  159. package/dist/utils/parse.js +51 -0
  160. package/dist/utils/text.d.ts +4 -0
  161. package/dist/utils/text.js +14 -0
  162. package/dist/utils/transform.d.ts +8 -0
  163. package/dist/utils/transform.js +48 -0
  164. package/package.json +57 -29
  165. package/src/auth.ts +786 -0
  166. package/src/ceremonies/index.ts +8 -2
  167. package/src/choice-token.ts +165 -0
  168. package/src/cli/commands.ts +34 -11
  169. package/src/cli/create.ts +254 -128
  170. package/src/cli/templates/provider/.dockerignore.tpl +22 -0
  171. package/src/cli/templates/provider/.gitignore.tpl +22 -0
  172. package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
  173. package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
  174. package/src/cli/templates/provider/README.md.tpl +87 -7
  175. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  176. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  177. package/src/cli/templates/provider/index.ts.tpl +5 -47
  178. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  179. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  180. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  181. package/src/cli/templates/provider/operations/ping.ts.tpl +24 -0
  182. package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  183. package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  184. package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  185. package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
  186. package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  187. package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  188. package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
  189. package/src/cli/templates/provider/start.ts.tpl +1 -1
  190. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  191. package/src/config/loader.ts +1224 -9
  192. package/src/contract-json.ts +75 -0
  193. package/src/contract-serialization.ts +89 -0
  194. package/src/contract-types.ts +52 -0
  195. package/src/contract.ts +216 -0
  196. package/src/define.ts +1820 -70
  197. package/src/errors.ts +27 -0
  198. package/src/i18n/catalog.ts +277 -0
  199. package/src/i18n/index.ts +2 -0
  200. package/src/i18n/keys.ts +64 -0
  201. package/src/index.ts +189 -9
  202. package/src/lint.ts +580 -73
  203. package/src/observability.ts +41 -0
  204. package/src/provider.ts +131 -4
  205. package/src/public-schema-field-lint.ts +237 -0
  206. package/src/runtime/auth-flow.ts +9 -0
  207. package/src/runtime/browser.ts +1054 -51
  208. package/src/runtime/cache.ts +528 -0
  209. package/src/runtime/choice.ts +760 -0
  210. package/src/runtime/executor.ts +32 -3
  211. package/src/runtime/http.ts +980 -195
  212. package/src/runtime/insights.ts +11 -11
  213. package/src/runtime/instrumentation.ts +12 -4
  214. package/src/runtime/key-derivation.ts +1 -1
  215. package/src/runtime/keyring.ts +4 -3
  216. package/src/runtime/proxy-errors.ts +132 -0
  217. package/src/runtime/proxy-telemetry.ts +253 -0
  218. package/src/runtime/redis.ts +116 -0
  219. package/src/runtime/request-options.ts +66 -0
  220. package/src/runtime/state.ts +563 -0
  221. package/src/runtime/stealth.ts +1336 -0
  222. package/src/runtime/stt.ts +629 -0
  223. package/src/runtime/trace.ts +1 -1
  224. package/src/schema.ts +363 -1
  225. package/src/server/serve.ts +1192 -75
  226. package/src/server/types.ts +37 -0
  227. package/src/stream.ts +210 -0
  228. package/src/testing/run.ts +40 -6
  229. package/src/types.ts +1283 -59
  230. package/src/runtime/tls.ts +0 -434
  231. package/src/types/playwright-stealth.d.ts +0 -9
package/SUBMISSION.md ADDED
@@ -0,0 +1,86 @@
1
+ # Provider bounty submission guide
2
+
3
+ This guide is for public bounty contributors who only have access to the published `@apifuse/provider-sdk` package. You do **not** need the private APIFuse monorepo to build or pre-check a Provider.
4
+
5
+ ## SDK-only workspace workflow
6
+
7
+ ```bash
8
+ bunx @apifuse/provider-sdk@beta create my-provider --yes
9
+ cd my-provider
10
+ bun run check
11
+ bun run test
12
+ bun run submit-check -- --smoke
13
+ bun run dev
14
+ ```
15
+
16
+ `bun run submit-check -- --smoke` runs the generated `apifuse submit-check . --markdown submission-report.md` script with measured smoke enabled.
17
+
18
+ ## What submit-check scores
19
+
20
+ `apifuse submit-check` produces a 100-point review-readiness score and a verdict:
21
+
22
+ - `ready` — no blockers or warnings and score is at least 90.
23
+ - `reviewable_with_warnings` — no blockers, but reviewers should inspect the warnings.
24
+ - `blocked` — at least one hard blocker must be fixed before review.
25
+
26
+ The score is a triage aid, not a payout guarantee. Maintainers still review correctness, upstream behavior, policy fit, and bounty scope.
27
+
28
+ | Category | Points | Examples |
29
+ |---|---:|---|
30
+ | Definition & metadata | 15 | `defineProvider`, package, Dockerfile, SDK structural checks |
31
+ | Operations & schemas | 15 | strong descriptions, annotations, input/output schemas |
32
+ | Fixtures & tests | 15 | bidirectional fixtures that parse against schemas |
33
+ | Health coverage | 15 | real `healthCheck` or specific `healthCheckUnsupported.reason` |
34
+ | Runtime/local smoke | 10 | `/health` and at least one `POST /v1/{operation}` note |
35
+ | Auth/credential safety | 10 | auth mode and credential declarations are consistent |
36
+ | Security hygiene | 10 | no high-confidence secrets in shareable files |
37
+ | Docs/submission evidence | 10 | README Parameters, Response, Example, submit-check guidance |
38
+
39
+ ## Hard blockers
40
+
41
+ Fix all blockers before submitting:
42
+
43
+ - `bun run check` failures.
44
+ - Provider cannot be imported from `index.ts`.
45
+ - Missing handler/input/output for any Operation.
46
+ - Missing fixture request or response.
47
+ - Fixture data does not parse against schemas.
48
+ - Missing `healthCheck` or `healthCheckUnsupported` on any Operation.
49
+ - Credential-backed auth mode without declared credential keys.
50
+ - High-confidence secret or token material in source, README, package metadata, or fixtures.
51
+ - SDK-native source blockers: prefixed Provider ids, `vendor/` SDK shims or imports, raw `.describe()` prose instead of `describeKey`, raw global `fetch()` calls, and excessive `as Type` assertions.
52
+
53
+ Warnings do not fail the command, but they should be addressed when practical. For example, the generated starter `ping` operation warns because it is not a real upstream-backed bounty Operation. SDK-native warnings also flag moderate `as Type` assertion counts and credentialed Providers that never reference `ctx.credential`.
54
+
55
+ ## Measured local smoke
56
+
57
+ `submit-check` does not call live upstream APIs by default. For smoke scoring,
58
+ run measured smoke so the CLI boots the Provider, checks `/health`, and POSTs
59
+ each operation fixture:
60
+
61
+ ```bash
62
+ bun run submit-check -- --smoke
63
+ ```
64
+
65
+ `APIFUSE__PROVIDER__*` env vars enable live upstream calls. Their absence can
66
+ still produce 7/10 smoke credit when every operation routes and returns a
67
+ structured provider error envelope, but zero schema-valid successes are observed.
68
+ Never paste real credentials, personal data, account numbers, access tokens, cookies, or unredacted upstream responses into workspace PR comments, chat, or reports.
69
+
70
+ ## Submission evidence checklist
71
+
72
+ Include the following when you submit a Provider for review:
73
+
74
+ - Provider SDK version, for example `bun pm ls @apifuse/provider-sdk` or package.json dependency.
75
+ - Provider id, version, runtime, and auth mode.
76
+ - Operation list with input/output summaries.
77
+ - Health coverage table: one row per Operation with `healthCheck` or `healthCheckUnsupported` and rationale.
78
+ - `bun run check` output.
79
+ - `bun run test` output.
80
+ - `submission-report.md` generated by `bun run submit-check`.
81
+ - Local smoke evidence for `/health` and at least one `POST /v1/{operation}` call.
82
+ - Known upstream constraints: rate limits, paid/destructive calls, auth limits, flaky endpoints, or unsupported probes.
83
+
84
+ ## Maintainer-owned follow-up
85
+
86
+ After workspace evidence is submitted, APIFuse maintainers import accepted standalone Provider work into the private monorepo and run internal registry, generated artifact, deployment projection, and CI checks. External contributors are not expected to run those private checks.
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
7
  import { z } from "zod";
8
8
 
9
9
  import type { ProviderDefinition } from "../src";
10
- import { lintProvider } from "../src/lint";
10
+ import { lintProvider, type ProviderLintMode } from "../src/lint";
11
11
  import { safeParseSchemaSync } from "../src/schema";
12
12
 
13
13
  const HELP_TEXT = `Usage: apifuse check [path]
14
- Example: apifuse check providers/airkorea
14
+ Example: apifuse check providers/korea-air-quality
15
15
  Default: apifuse check .`;
16
16
 
17
17
  export type CheckResult = {
@@ -20,6 +20,10 @@ export type CheckResult = {
20
20
  details?: string[];
21
21
  };
22
22
 
23
+ export type RunChecksOptions = {
24
+ lintMode?: ProviderLintMode;
25
+ };
26
+
23
27
  type SafeParseResult =
24
28
  | { success: true; data: unknown }
25
29
  | { success: false; error: unknown };
@@ -104,7 +108,10 @@ function resolveFromParents(inputPath: string): string {
104
108
  }
105
109
  }
106
110
 
107
- export async function runChecks(providerRoot: string): Promise<CheckResult[]> {
111
+ export async function runChecks(
112
+ providerRoot: string,
113
+ options: RunChecksOptions = {},
114
+ ): Promise<CheckResult[]> {
108
115
  const indexPath = resolve(providerRoot, "index.ts");
109
116
  const dockerfilePath = resolve(providerRoot, "Dockerfile");
110
117
  const packageJsonPath = resolve(providerRoot, "package.json");
@@ -113,19 +120,61 @@ export async function runChecks(providerRoot: string): Promise<CheckResult[]> {
113
120
  ? await import(pathToFileURL(indexPath).href)
114
121
  : undefined;
115
122
  const provider = assertProviderDefinition(providerModule?.default);
123
+ const providerSourceFiles = collectProviderSourceFiles(providerRoot);
116
124
 
117
125
  return [
118
126
  checkIndex(indexPath, provider),
119
127
  checkOperations(provider),
120
128
  checkFixtures(provider),
121
129
  checkSchemas(provider),
122
- checkAuthoringLint(provider),
130
+ checkAuthoringLint(provider, providerSourceFiles, options.lintMode),
123
131
  checkProviderMetadata(provider),
124
132
  checkDockerfile(dockerfilePath),
125
133
  checkPackageJson(packageJsonPath),
126
134
  ];
127
135
  }
128
136
 
137
+ function isScannableProviderSourceFile(relativePath: string): boolean {
138
+ return (
139
+ /\.(?:ts|tsx|js|jsx|mjs|cjs|sh|bash)$/.test(relativePath) ||
140
+ /(?:^|\/)Dockerfile(?:\.|$)/.test(relativePath) ||
141
+ /(?:^|\/)entrypoint(?:\.|$)/.test(relativePath)
142
+ );
143
+ }
144
+
145
+ function collectProviderSourceFiles(
146
+ providerRoot: string,
147
+ ): Record<string, string> {
148
+ const sources: Record<string, string> = {};
149
+ const skipDirectories = new Set([
150
+ ".git",
151
+ "node_modules",
152
+ "dist",
153
+ "build",
154
+ ".next",
155
+ ]);
156
+ const visit = (directory: string) => {
157
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
158
+ const path = resolve(directory, entry.name);
159
+ if (entry.isDirectory()) {
160
+ if (!skipDirectories.has(entry.name)) {
161
+ visit(path);
162
+ }
163
+ continue;
164
+ }
165
+ if (
166
+ !entry.isFile() ||
167
+ !isScannableProviderSourceFile(path.slice(providerRoot.length + 1))
168
+ ) {
169
+ continue;
170
+ }
171
+ sources[path.slice(providerRoot.length + 1)] = readFileSync(path, "utf8");
172
+ }
173
+ };
174
+ visit(providerRoot);
175
+ return sources;
176
+ }
177
+
129
178
  function checkIndex(
130
179
  indexPath: string,
131
180
  provider: ProviderDefinition | undefined,
@@ -258,6 +307,8 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
258
307
 
259
308
  function checkAuthoringLint(
260
309
  provider: ProviderDefinition | undefined,
310
+ providerSourceFiles: Record<string, string>,
311
+ lintMode: ProviderLintMode = "official",
261
312
  ): CheckResult {
262
313
  if (!provider) {
263
314
  return {
@@ -266,7 +317,10 @@ function checkAuthoringLint(
266
317
  };
267
318
  }
268
319
 
269
- const diagnostics = lintProvider(provider);
320
+ const diagnostics = lintProvider(
321
+ { ...provider, providerSourceFiles },
322
+ { mode: lintMode },
323
+ );
270
324
  const errors = diagnostics.filter(
271
325
  (diagnostic) => diagnostic.level === "error",
272
326
  );
@@ -2,21 +2,25 @@
2
2
 
3
3
  import { existsSync } from "node:fs";
4
4
  import { dirname, relative, resolve } from "node:path";
5
-
6
5
  import type { ProviderDefinition } from "../src";
7
6
  import {
8
7
  createBrowserClient,
9
8
  createCredentialContext,
10
9
  createEnvContext,
11
10
  createHttpClient,
12
- createTlsClient,
11
+ createProviderCache,
12
+ createProviderChoiceContext,
13
+ createStealthClient,
14
+ createSttClientFromEnv,
15
+ PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
13
16
  ProviderError,
14
17
  } from "../src";
18
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state";
15
19
  import { createTraceContext } from "../src/runtime/trace";
16
20
  import type { BrowserClient, ProviderContext } from "../src/types";
17
21
 
18
22
  const HELP_TEXT = `Usage: apifuse dev [path]
19
- Example: apifuse dev providers/airkorea
23
+ Example: apifuse dev providers/korea-air-quality
20
24
  Default: apifuse dev .`;
21
25
 
22
26
  export async function main() {
@@ -35,7 +39,7 @@ export async function main() {
35
39
  );
36
40
 
37
41
  const { startDevServer } = await import("../src/dev");
38
- const port = Number(process.env.PORT) || 3900;
42
+ const port = Number(process.env.APIFUSE__RUNTIME__PORT) || 3900;
39
43
 
40
44
  startDevServer(provider, { port });
41
45
 
@@ -74,19 +78,35 @@ export async function main() {
74
78
  export function createProviderContext(provider: ProviderDefinition): {
75
79
  ctx: ProviderContext;
76
80
  } {
81
+ const env = createEnvContext([
82
+ ...(provider.secrets?.map((secret) => secret.name) ?? []),
83
+ PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
84
+ ]);
85
+ const credential = createCredentialContext();
86
+ const state = createMemoryProviderRuntimeState();
77
87
  const ctx: ProviderContext = {
78
- env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
79
- credential: createCredentialContext(),
88
+ env,
89
+ credential,
80
90
  auth: createUnsupportedAuthStub(),
81
91
  browser:
82
92
  provider.runtime === "browser"
83
93
  ? createBrowserClient({
94
+ allowedHosts: provider.allowedHosts,
84
95
  engine: provider.browser?.engine ?? "playwright-stealth",
85
96
  })
86
97
  : createUnsupportedBrowserStub(),
87
98
  http: createHttpClient(),
99
+ cache: createProviderCache({ providerId: provider.id }),
100
+ state,
88
101
  trace: createTraceContext(),
89
- tls: createTlsClient("http://localhost"),
102
+ stealth: createStealthClient("http://localhost"),
103
+ stt: createSttClientFromEnv(provider.stt),
104
+ choice: createProviderChoiceContext({
105
+ providerId: provider.id,
106
+ env,
107
+ credential,
108
+ state,
109
+ }),
90
110
  };
91
111
 
92
112
  return { ctx };
@@ -129,7 +149,9 @@ function renderHotReloadCommand(providerPath: string, port: number): string {
129
149
  const devEntry = resolve(providerPath, "dev.ts");
130
150
  if (existsSync(devEntry)) {
131
151
  const relativeDevEntry = relative(process.cwd(), devEntry) || "dev.ts";
132
- const portPrefix = process.env.PORT ? `PORT=${port} ` : "";
152
+ const portPrefix = process.env.APIFUSE__RUNTIME__PORT
153
+ ? `APIFUSE__RUNTIME__PORT=${port} `
154
+ : "";
133
155
  return `${portPrefix}bun --hot ${relativeDevEntry}`;
134
156
  }
135
157
  return "rerun `apifuse dev` after edits (no dev.ts entrypoint found)";
@@ -142,6 +164,7 @@ function shellSingleQuote(value: string): string {
142
164
  function createUnsupportedBrowserStub(): BrowserClient {
143
165
  return {
144
166
  engine: "playwright-stealth",
167
+ async close() {},
145
168
  async newPage() {
146
169
  throw new ProviderError(
147
170
  "Browser runtime is not enabled for this provider",
@@ -151,6 +174,33 @@ function createUnsupportedBrowserStub(): BrowserClient {
151
174
  },
152
175
  );
153
176
  },
177
+ async rawPage() {
178
+ throw new ProviderError(
179
+ "Browser runtime is not enabled for this provider",
180
+ {
181
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
182
+ fix: 'Set provider runtime to "browser" and APIFUSE__CDP_POOL__URL to use ctx.browser.rawPage',
183
+ },
184
+ );
185
+ },
186
+ async withIsolatedContext() {
187
+ throw new ProviderError(
188
+ "Browser runtime is not enabled for this provider",
189
+ {
190
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
191
+ fix: 'Set provider runtime to "browser" to use ctx.browser.withIsolatedContext',
192
+ },
193
+ );
194
+ },
195
+ async solveChallenge() {
196
+ throw new ProviderError(
197
+ "Browser runtime is not enabled for this provider",
198
+ {
199
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
200
+ fix: 'Set provider runtime to "browser" to use ctx.browser.solveChallenge',
201
+ },
202
+ );
203
+ },
154
204
  };
155
205
  }
156
206
 
@@ -33,9 +33,33 @@ const requiredPaths = [
33
33
  "bin/apifuse.ts",
34
34
  "bin/apifuse-create.ts",
35
35
  "bin/apifuse-pack-smoke.ts",
36
+ "bin/apifuse-submit-check.ts",
37
+ "SUBMISSION.md",
36
38
  "src/cli/create.ts",
39
+ "src/cli/templates/provider/.dockerignore.tpl",
40
+ "src/cli/templates/provider/.gitignore.tpl",
41
+ "src/cli/templates/provider/Dockerfile.tpl",
37
42
  "src/cli/templates/provider/index.ts.tpl",
38
43
  "src/cli/templates/provider/README.md.tpl",
44
+ "src/cli/templates/provider/meta.ts.tpl",
45
+ "src/cli/templates/provider/domain/README.md.tpl",
46
+ "src/cli/templates/provider/mappers/README.md.tpl",
47
+ "src/cli/templates/provider/operations/index.ts.tpl",
48
+ "src/cli/templates/provider/operations/ping.ts.tpl",
49
+ "src/cli/templates/provider/schemas/ping.ts.tpl",
50
+ "src/cli/templates/provider/upstream/README.md.tpl",
51
+ "dist/cli/templates/provider/.dockerignore.tpl",
52
+ "dist/cli/templates/provider/.gitignore.tpl",
53
+ "dist/cli/templates/provider/Dockerfile.tpl",
54
+ "dist/cli/templates/provider/index.ts.tpl",
55
+ "dist/cli/templates/provider/README.md.tpl",
56
+ "dist/cli/templates/provider/meta.ts.tpl",
57
+ "dist/cli/templates/provider/domain/README.md.tpl",
58
+ "dist/cli/templates/provider/mappers/README.md.tpl",
59
+ "dist/cli/templates/provider/operations/index.ts.tpl",
60
+ "dist/cli/templates/provider/operations/ping.ts.tpl",
61
+ "dist/cli/templates/provider/schemas/ping.ts.tpl",
62
+ "dist/cli/templates/provider/upstream/README.md.tpl",
39
63
  ];
40
64
  const forbiddenMatches = filePaths.filter(
41
65
  (path) =>
@@ -111,9 +135,15 @@ function assertPublicSmokeDocs(label: string, content: string): void {
111
135
  );
112
136
  }
113
137
 
114
- if (!content.includes("bun pm untrusted")) {
138
+ if (!content.includes("impit")) {
115
139
  throw new Error(
116
- `${label} must include Bun trusted-dependency troubleshooting for TLS/browser bounties.`,
140
+ `${label} must include impit stealth runtime guidance for TLS/browser bounties.`,
141
+ );
142
+ }
143
+
144
+ if (!content.includes("submit-check")) {
145
+ throw new Error(
146
+ `${label} must document the submit-check pre-submission workflow.`,
117
147
  );
118
148
  }
119
149
 
@@ -39,17 +39,19 @@ const PING_RESPONSE_SCHEMA = z.object({
39
39
  error: z.unknown().optional(),
40
40
  });
41
41
 
42
- const KEEP_TEMP = process.env.APIFUSE_PACK_SMOKE_KEEP_TEMP === "1";
42
+ const KEEP_TEMP = process.env.APIFUSE__PACK_SMOKE__KEEP_TEMP === "1";
43
43
 
44
44
  const tempRoot = mkdtempSync(
45
45
  join(tmpdir(), "apifuse-provider-sdk-pack-smoke-"),
46
46
  );
47
47
  const packDir = join(tempRoot, "pack");
48
48
  const consumerDir = join(tempRoot, "consumer");
49
+ const externalWorkspaceDir = join(tempRoot, "external-workspace");
49
50
 
50
51
  try {
51
52
  mkdirSync(packDir, { recursive: true });
52
53
  mkdirSync(consumerDir, { recursive: true });
54
+ mkdirSync(join(externalWorkspaceDir, "providers"), { recursive: true });
53
55
 
54
56
  const packed = packSdk(packDir);
55
57
  const tarballPath = resolve(packDir, packed.filename);
@@ -93,9 +95,15 @@ try {
93
95
 
94
96
  const generatedProviderDir = join(consumerDir, "dx-smoke");
95
97
  run("bun", ["run", "check"], generatedProviderDir);
98
+ run("bun", ["run", "submit-check"], generatedProviderDir);
96
99
  run("bun", ["run", "test"], generatedProviderDir);
97
100
  assertGeneratedReadme(generatedProviderDir);
98
101
  await smokeGeneratedDevServer(generatedProviderDir);
102
+ assertExternalWorkspaceTopology(
103
+ cliBin,
104
+ externalWorkspaceDir,
105
+ tarballSpecifier,
106
+ );
99
107
 
100
108
  console.log(
101
109
  `Provider SDK packed-artifact smoke passed: ${tarballPath} -> ${generatedProviderDir}`,
@@ -108,6 +116,103 @@ try {
108
116
  }
109
117
  }
110
118
 
119
+ function assertExternalWorkspaceTopology(
120
+ cliBin: string,
121
+ externalWorkspaceDir: string,
122
+ tarballSpecifier: string,
123
+ ): void {
124
+ writeFileSync(
125
+ join(externalWorkspaceDir, "package.json"),
126
+ `${JSON.stringify(
127
+ {
128
+ private: true,
129
+ type: "module",
130
+ workspaces: ["providers/*"],
131
+ },
132
+ null,
133
+ 2,
134
+ )}\n`,
135
+ );
136
+
137
+ run(
138
+ "bun",
139
+ [
140
+ cliBin,
141
+ "create",
142
+ "external-workspace-smoke",
143
+ "--yes",
144
+ "--json",
145
+ "--sdk-specifier",
146
+ tarballSpecifier,
147
+ ],
148
+ externalWorkspaceDir,
149
+ );
150
+
151
+ const generatedProviderDir = join(
152
+ externalWorkspaceDir,
153
+ "external-workspace-smoke",
154
+ );
155
+ const forbiddenProviderDir = join(
156
+ externalWorkspaceDir,
157
+ "providers",
158
+ "external-workspace-smoke",
159
+ );
160
+ if (!existsSync(generatedProviderDir)) {
161
+ throw new Error(
162
+ "Public create must generate a one-provider repository at <name>/ even when providers/ exists.",
163
+ );
164
+ }
165
+ if (existsSync(forbiddenProviderDir)) {
166
+ throw new Error(
167
+ "Public create must not generate providers/<name>/ in external bounty workspaces.",
168
+ );
169
+ }
170
+
171
+ const packageJson = JSON.parse(
172
+ readFileSync(join(generatedProviderDir, "package.json"), "utf8"),
173
+ );
174
+ const sdkDependency = packageJson?.dependencies?.["@apifuse/provider-sdk"];
175
+ if (sdkDependency !== tarballSpecifier) {
176
+ throw new Error(
177
+ `Expected generated provider to depend on packed SDK ${tarballSpecifier}, got ${sdkDependency}`,
178
+ );
179
+ }
180
+ if (JSON.stringify(packageJson).includes("workspace:")) {
181
+ throw new Error(
182
+ "External bounty workspace scaffold must not contain workspace: dependencies.",
183
+ );
184
+ }
185
+
186
+ run("bun", ["install"], generatedProviderDir);
187
+ run("bun", ["run", "check"], generatedProviderDir);
188
+ run("bun", ["run", "submit-check"], generatedProviderDir);
189
+ run("bun", ["run", "test"], generatedProviderDir);
190
+
191
+ const monorepoAttempt = spawnSync(
192
+ "bun",
193
+ [cliBin, "create", "bad-monorepo-smoke", "--preset", "monorepo", "--yes"],
194
+ {
195
+ cwd: externalWorkspaceDir,
196
+ env: { ...process.env, APIFUSE__SDK__SPECIFIER: tarballSpecifier },
197
+ encoding: "utf8",
198
+ stdio: ["ignore", "pipe", "pipe"],
199
+ },
200
+ );
201
+ if (monorepoAttempt.status === 0) {
202
+ throw new Error(
203
+ "--preset monorepo must reject outside the private APIFuse monorepo.",
204
+ );
205
+ }
206
+ const rejectionOutput = `${monorepoAttempt.stdout}\n${monorepoAttempt.stderr}`;
207
+ if (
208
+ !rejectionOutput.includes(
209
+ "Monorepo preset is internal to the APIFuse repository",
210
+ )
211
+ ) {
212
+ throw new Error(`Unexpected monorepo rejection output: ${rejectionOutput}`);
213
+ }
214
+ }
215
+
111
216
  function packSdk(destination: string): { filename: string } {
112
217
  const raw = execFileSync(
113
218
  "npm",
@@ -161,9 +266,14 @@ function assertGeneratedReadme(providerDir: string): void {
161
266
  "Generated README is missing browser runtime troubleshooting guidance.",
162
267
  );
163
268
  }
164
- if (!readme.includes("bun pm untrusted")) {
269
+ if (!readme.includes("impit")) {
165
270
  throw new Error(
166
- "Generated README is missing Bun trusted-dependency troubleshooting guidance.",
271
+ "Generated README is missing impit stealth runtime guidance.",
272
+ );
273
+ }
274
+ if (!readme.includes("bun run submit-check")) {
275
+ throw new Error(
276
+ "Generated README must document the submit-check pre-submission workflow.",
167
277
  );
168
278
  }
169
279
  if (!readme.includes("bun run record -- --operation <operation>")) {
@@ -177,7 +287,8 @@ async function smokeGeneratedDevServer(providerDir: string): Promise<void> {
177
287
  const port = await getAvailablePort();
178
288
  const server = spawn("bun", ["run", "dev"], {
179
289
  cwd: providerDir,
180
- env: { ...process.env, PORT: String(port) },
290
+ env: { ...process.env, APIFUSE__RUNTIME__PORT: String(port) },
291
+ detached: process.platform !== "win32",
181
292
  stdio: ["ignore", "pipe", "pipe"],
182
293
  });
183
294
  let output = "";
@@ -280,11 +391,11 @@ async function stopServer(server: ChildProcess): Promise<void> {
280
391
  if (server.exitCode !== null) {
281
392
  return;
282
393
  }
283
- server.kill("SIGTERM");
394
+ killProcessTree(server, "SIGTERM");
284
395
  await new Promise<void>((resolvePromise) => {
285
396
  const timeout = setTimeout(() => {
286
397
  if (server.exitCode === null) {
287
- server.kill("SIGKILL");
398
+ killProcessTree(server, "SIGKILL");
288
399
  }
289
400
  resolvePromise();
290
401
  }, 2_000);
@@ -294,3 +405,19 @@ async function stopServer(server: ChildProcess): Promise<void> {
294
405
  });
295
406
  });
296
407
  }
408
+
409
+ function killProcessTree(server: ChildProcess, signal: NodeJS.Signals): void {
410
+ if (server.pid === undefined) {
411
+ return;
412
+ }
413
+
414
+ try {
415
+ if (process.platform === "win32") {
416
+ server.kill(signal);
417
+ return;
418
+ }
419
+ process.kill(-server.pid, signal);
420
+ } catch {
421
+ server.kill(signal);
422
+ }
423
+ }