@apifuse/provider-sdk 2.1.0-beta.0 → 2.1.0-beta.10

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 (213) hide show
  1. package/AUTHORING.md +218 -21
  2. package/CHANGELOG.md +54 -0
  3. package/README.md +147 -10
  4. package/SUBMISSION.md +87 -0
  5. package/bin/apifuse-check.ts +86 -4
  6. package/bin/apifuse-dev.ts +87 -13
  7. package/bin/apifuse-pack-check.ts +120 -0
  8. package/bin/apifuse-pack-smoke.ts +423 -0
  9. package/bin/apifuse-perf.ts +142 -49
  10. package/bin/apifuse-record.ts +182 -104
  11. package/bin/apifuse-submit-check.ts +2538 -0
  12. package/bin/apifuse.ts +1 -1
  13. package/dist/ceremonies/index.d.ts +41 -0
  14. package/dist/ceremonies/index.js +490 -0
  15. package/dist/choice-token.d.ts +24 -0
  16. package/dist/choice-token.js +74 -0
  17. package/dist/cli/commands.d.ts +10 -0
  18. package/dist/cli/commands.js +80 -0
  19. package/dist/cli/create.d.ts +47 -0
  20. package/dist/cli/create.js +762 -0
  21. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  22. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  23. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  24. package/dist/cli/templates/provider/README.md.tpl +160 -0
  25. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  26. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  27. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  28. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  29. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  30. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  31. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  32. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  33. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  34. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  35. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  36. package/dist/config/loader.d.ts +107 -0
  37. package/dist/config/loader.js +935 -0
  38. package/dist/contract-json.d.ts +9 -0
  39. package/dist/contract-json.js +51 -0
  40. package/dist/contract-serialization.d.ts +4 -0
  41. package/dist/contract-serialization.js +78 -0
  42. package/dist/contract-types.d.ts +49 -0
  43. package/dist/contract-types.js +1 -0
  44. package/dist/contract.d.ts +6 -0
  45. package/dist/contract.js +155 -0
  46. package/dist/define.d.ts +97 -0
  47. package/dist/define.js +1320 -0
  48. package/dist/dev.d.ts +9 -0
  49. package/dist/dev.js +15 -0
  50. package/dist/errors.d.ts +59 -0
  51. package/dist/errors.js +97 -0
  52. package/dist/i18n/catalog.d.ts +29 -0
  53. package/dist/i18n/catalog.js +159 -0
  54. package/dist/i18n/index.d.ts +2 -0
  55. package/dist/i18n/index.js +2 -0
  56. package/dist/i18n/keys.d.ts +10 -0
  57. package/dist/i18n/keys.js +34 -0
  58. package/dist/index.d.ts +41 -0
  59. package/dist/index.js +37 -0
  60. package/dist/lint.d.ts +73 -0
  61. package/dist/lint.js +702 -0
  62. package/dist/observability.d.ts +5 -0
  63. package/dist/observability.js +39 -0
  64. package/dist/provider.d.ts +9 -0
  65. package/dist/provider.js +8 -0
  66. package/dist/public-schema-field-lint.d.ts +2 -0
  67. package/dist/public-schema-field-lint.js +158 -0
  68. package/dist/recipes/gov-api.d.ts +19 -0
  69. package/dist/recipes/gov-api.js +72 -0
  70. package/dist/recipes/rest-api.d.ts +21 -0
  71. package/dist/recipes/rest-api.js +115 -0
  72. package/dist/runtime/auth-flow.d.ts +14 -0
  73. package/dist/runtime/auth-flow.js +44 -0
  74. package/dist/runtime/browser.d.ts +25 -0
  75. package/dist/runtime/browser.js +1034 -0
  76. package/dist/runtime/cache.d.ts +10 -0
  77. package/dist/runtime/cache.js +372 -0
  78. package/dist/runtime/choice.d.ts +15 -0
  79. package/dist/runtime/choice.js +435 -0
  80. package/dist/runtime/credential.d.ts +8 -0
  81. package/dist/runtime/credential.js +61 -0
  82. package/dist/runtime/env.d.ts +2 -0
  83. package/dist/runtime/env.js +10 -0
  84. package/dist/runtime/executor.d.ts +16 -0
  85. package/dist/runtime/executor.js +51 -0
  86. package/dist/runtime/http.d.ts +8 -0
  87. package/dist/runtime/http.js +706 -0
  88. package/dist/runtime/insights.d.ts +9 -0
  89. package/dist/runtime/insights.js +324 -0
  90. package/dist/runtime/instrumentation.d.ts +8 -0
  91. package/dist/runtime/instrumentation.js +269 -0
  92. package/dist/runtime/key-derivation.d.ts +24 -0
  93. package/dist/runtime/key-derivation.js +73 -0
  94. package/dist/runtime/keyring.d.ts +25 -0
  95. package/dist/runtime/keyring.js +93 -0
  96. package/dist/runtime/namespace.d.ts +9 -0
  97. package/dist/runtime/namespace.js +19 -0
  98. package/dist/runtime/otlp.d.ts +39 -0
  99. package/dist/runtime/otlp.js +103 -0
  100. package/dist/runtime/perf.d.ts +12 -0
  101. package/dist/runtime/perf.js +52 -0
  102. package/dist/runtime/prevalidate.d.ts +12 -0
  103. package/dist/runtime/prevalidate.js +173 -0
  104. package/dist/runtime/provider.d.ts +2 -0
  105. package/dist/runtime/provider.js +11 -0
  106. package/dist/runtime/proxy-errors.d.ts +21 -0
  107. package/dist/runtime/proxy-errors.js +83 -0
  108. package/dist/runtime/proxy-telemetry.d.ts +8 -0
  109. package/dist/runtime/proxy-telemetry.js +174 -0
  110. package/dist/runtime/redis.d.ts +17 -0
  111. package/dist/runtime/redis.js +82 -0
  112. package/dist/runtime/request-options.d.ts +3 -0
  113. package/dist/runtime/request-options.js +42 -0
  114. package/dist/runtime/state.d.ts +17 -0
  115. package/dist/runtime/state.js +344 -0
  116. package/dist/runtime/stealth.d.ts +18 -0
  117. package/dist/runtime/stealth.js +834 -0
  118. package/dist/runtime/stt.d.ts +22 -0
  119. package/dist/runtime/stt.js +480 -0
  120. package/dist/runtime/trace.d.ts +26 -0
  121. package/dist/runtime/trace.js +142 -0
  122. package/dist/runtime/waterfall.d.ts +12 -0
  123. package/dist/runtime/waterfall.js +147 -0
  124. package/dist/schema.d.ts +74 -0
  125. package/dist/schema.js +243 -0
  126. package/dist/serve.d.ts +1 -0
  127. package/dist/serve.js +1 -0
  128. package/dist/server/index.d.ts +3 -0
  129. package/dist/server/index.js +2 -0
  130. package/dist/server/serve.d.ts +64 -0
  131. package/dist/server/serve.js +1110 -0
  132. package/dist/server/types.d.ts +136 -0
  133. package/dist/server/types.js +86 -0
  134. package/dist/stealth/profiles.d.ts +4 -0
  135. package/dist/stealth/profiles.js +259 -0
  136. package/dist/stream.d.ts +44 -0
  137. package/dist/stream.js +151 -0
  138. package/dist/testing/helpers.d.ts +23 -0
  139. package/dist/testing/helpers.js +95 -0
  140. package/dist/testing/index.d.ts +2 -0
  141. package/dist/testing/index.js +2 -0
  142. package/dist/testing/run.d.ts +34 -0
  143. package/dist/testing/run.js +303 -0
  144. package/dist/types.d.ts +1326 -0
  145. package/dist/types.js +61 -0
  146. package/dist/utils/date.d.ts +6 -0
  147. package/dist/utils/date.js +101 -0
  148. package/dist/utils/parse.d.ts +16 -0
  149. package/dist/utils/parse.js +51 -0
  150. package/dist/utils/text.d.ts +4 -0
  151. package/dist/utils/text.js +14 -0
  152. package/dist/utils/transform.d.ts +8 -0
  153. package/dist/utils/transform.js +48 -0
  154. package/package.json +57 -29
  155. package/src/ceremonies/index.ts +30 -3
  156. package/src/choice-token.ts +165 -0
  157. package/src/cli/commands.ts +34 -11
  158. package/src/cli/create.ts +214 -52
  159. package/src/cli/templates/provider/.dockerignore.tpl +22 -0
  160. package/src/cli/templates/provider/.gitignore.tpl +22 -0
  161. package/src/cli/templates/provider/README.md.tpl +134 -2
  162. package/src/cli/templates/provider/dev.ts.tpl +1 -1
  163. package/src/cli/templates/provider/domain/README.md.tpl +3 -0
  164. package/src/cli/templates/provider/index.ts.tpl +5 -44
  165. package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
  166. package/src/cli/templates/provider/meta.ts.tpl +7 -0
  167. package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
  168. package/src/cli/templates/provider/operations/ping.ts.tpl +24 -0
  169. package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  170. package/src/cli/templates/provider/start.ts.tpl +1 -1
  171. package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
  172. package/src/config/loader.ts +1282 -7
  173. package/src/contract-json.ts +75 -0
  174. package/src/contract-serialization.ts +89 -0
  175. package/src/contract-types.ts +52 -0
  176. package/src/contract.ts +215 -0
  177. package/src/define.ts +1726 -48
  178. package/src/errors.ts +27 -0
  179. package/src/i18n/catalog.ts +277 -0
  180. package/src/i18n/index.ts +2 -0
  181. package/src/i18n/keys.ts +64 -0
  182. package/src/index.ts +174 -15
  183. package/src/lint.ts +547 -73
  184. package/src/observability.ts +41 -0
  185. package/src/provider.ts +104 -5
  186. package/src/public-schema-field-lint.ts +237 -0
  187. package/src/runtime/auth-flow.ts +7 -0
  188. package/src/runtime/browser.ts +762 -51
  189. package/src/runtime/cache.ts +528 -0
  190. package/src/runtime/choice.ts +760 -0
  191. package/src/runtime/executor.ts +32 -3
  192. package/src/runtime/http.ts +945 -185
  193. package/src/runtime/insights.ts +11 -11
  194. package/src/runtime/instrumentation.ts +12 -4
  195. package/src/runtime/key-derivation.ts +1 -1
  196. package/src/runtime/keyring.ts +4 -3
  197. package/src/runtime/proxy-errors.ts +132 -0
  198. package/src/runtime/proxy-telemetry.ts +253 -0
  199. package/src/runtime/redis.ts +116 -0
  200. package/src/runtime/request-options.ts +66 -0
  201. package/src/runtime/state.ts +563 -0
  202. package/src/runtime/stealth.ts +1159 -0
  203. package/src/runtime/stt.ts +629 -0
  204. package/src/runtime/trace.ts +1 -1
  205. package/src/schema.ts +363 -1
  206. package/src/server/serve.ts +1172 -76
  207. package/src/server/types.ts +37 -0
  208. package/src/stream.ts +210 -0
  209. package/src/testing/run.ts +31 -5
  210. package/src/types.ts +1118 -44
  211. package/src/composite.ts +0 -43
  212. package/src/runtime/tls.ts +0 -425
  213. package/src/types/playwright-stealth.d.ts +0 -9
@@ -1,24 +1,29 @@
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, type ProviderLintMode } from "../src/lint";
10
11
  import { safeParseSchemaSync } from "../src/schema";
11
12
 
12
13
  const HELP_TEXT = `Usage: apifuse check [path]
13
- Example: apifuse check providers/airkorea
14
+ Example: apifuse check providers/korea-air-quality
14
15
  Default: apifuse check .`;
15
16
 
16
- type CheckResult = {
17
+ export type CheckResult = {
17
18
  message: string;
18
19
  passed: boolean;
19
20
  details?: string[];
20
21
  };
21
22
 
23
+ export type RunChecksOptions = {
24
+ lintMode?: ProviderLintMode;
25
+ };
26
+
22
27
  type SafeParseResult =
23
28
  | { success: true; data: unknown }
24
29
  | { success: false; error: unknown };
@@ -103,7 +108,10 @@ function resolveFromParents(inputPath: string): string {
103
108
  }
104
109
  }
105
110
 
106
- async function runChecks(providerRoot: string): Promise<CheckResult[]> {
111
+ export async function runChecks(
112
+ providerRoot: string,
113
+ options: RunChecksOptions = {},
114
+ ): Promise<CheckResult[]> {
107
115
  const indexPath = resolve(providerRoot, "index.ts");
108
116
  const dockerfilePath = resolve(providerRoot, "Dockerfile");
109
117
  const packageJsonPath = resolve(providerRoot, "package.json");
@@ -112,18 +120,61 @@ async function runChecks(providerRoot: string): Promise<CheckResult[]> {
112
120
  ? await import(pathToFileURL(indexPath).href)
113
121
  : undefined;
114
122
  const provider = assertProviderDefinition(providerModule?.default);
123
+ const providerSourceFiles = collectProviderSourceFiles(providerRoot);
115
124
 
116
125
  return [
117
126
  checkIndex(indexPath, provider),
118
127
  checkOperations(provider),
119
128
  checkFixtures(provider),
120
129
  checkSchemas(provider),
130
+ checkAuthoringLint(provider, providerSourceFiles, options.lintMode),
121
131
  checkProviderMetadata(provider),
122
132
  checkDockerfile(dockerfilePath),
123
133
  checkPackageJson(packageJsonPath),
124
134
  ];
125
135
  }
126
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
+
127
178
  function checkIndex(
128
179
  indexPath: string,
129
180
  provider: ProviderDefinition | undefined,
@@ -254,6 +305,37 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
254
305
  };
255
306
  }
256
307
 
308
+ function checkAuthoringLint(
309
+ provider: ProviderDefinition | undefined,
310
+ providerSourceFiles: Record<string, string>,
311
+ lintMode: ProviderLintMode = "official",
312
+ ): CheckResult {
313
+ if (!provider) {
314
+ return {
315
+ message: "Provider authoring lint has no error-level diagnostics",
316
+ passed: false,
317
+ };
318
+ }
319
+
320
+ const diagnostics = lintProvider(
321
+ { ...provider, providerSourceFiles },
322
+ { mode: lintMode },
323
+ );
324
+ const errors = diagnostics.filter(
325
+ (diagnostic) => diagnostic.level === "error",
326
+ );
327
+ const details = diagnostics.map((diagnostic) => {
328
+ const field = diagnostic.field ? `${diagnostic.field}: ` : "";
329
+ return `${diagnostic.level.toUpperCase()} ${diagnostic.rule} ${field}${diagnostic.message}`;
330
+ });
331
+
332
+ return {
333
+ message: "Provider authoring lint has no error-level diagnostics",
334
+ passed: errors.length === 0,
335
+ details,
336
+ };
337
+ }
338
+
257
339
  function checkProviderMetadata(
258
340
  provider: ProviderDefinition | undefined,
259
341
  ): CheckResult {
@@ -1,22 +1,26 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { existsSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
5
-
4
+ import { dirname, relative, resolve } from "node:path";
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
 
@@ -51,28 +55,58 @@ export async function main() {
51
55
  console.log(` POST http://localhost:${port}/auth/poll`);
52
56
  console.log(` POST http://localhost:${port}/auth/disconnect`);
53
57
 
58
+ const firstOperation = Object.keys(provider.operations)[0];
59
+ if (firstOperation) {
60
+ const sampleInput =
61
+ provider.operations[firstOperation]?.fixtures?.request ?? {};
62
+ const sampleBody = JSON.stringify({
63
+ requestId: `req_local_${firstOperation}`,
64
+ input: sampleInput,
65
+ headers: {},
66
+ });
67
+ console.log("\nSmoke:");
68
+ console.log(` curl -s http://localhost:${port}/health`);
69
+ console.log(
70
+ ` curl -s -X POST http://localhost:${port}/v1/${firstOperation} -H 'Content-Type: application/json' -d ${shellSingleQuote(sampleBody)}`,
71
+ );
72
+ }
73
+
54
74
  console.log("\nHot reload:");
55
- console.log(
56
- ` bun --hot ${resolveImportPath("apifuse-dev.ts")} ${args[0] ?? "."}`,
57
- );
75
+ console.log(` ${renderHotReloadCommand(providerPath, port)}`);
58
76
  }
59
77
 
60
78
  export function createProviderContext(provider: ProviderDefinition): {
61
79
  ctx: ProviderContext;
62
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();
63
87
  const ctx: ProviderContext = {
64
- env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
65
- credential: createCredentialContext(),
88
+ env,
89
+ credential,
66
90
  auth: createUnsupportedAuthStub(),
67
91
  browser:
68
92
  provider.runtime === "browser"
69
93
  ? createBrowserClient({
94
+ allowedHosts: provider.allowedHosts,
70
95
  engine: provider.browser?.engine ?? "playwright-stealth",
71
96
  })
72
97
  : createUnsupportedBrowserStub(),
73
98
  http: createHttpClient(),
99
+ cache: createProviderCache({ providerId: provider.id }),
100
+ state,
74
101
  trace: createTraceContext(),
75
- 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
+ }),
76
110
  };
77
111
 
78
112
  return { ctx };
@@ -111,13 +145,26 @@ function resolveFromParents(inputPath: string): string {
111
145
  }
112
146
  }
113
147
 
114
- function resolveImportPath(fileName: string): string {
115
- return resolve(process.cwd(), "bin", fileName);
148
+ function renderHotReloadCommand(providerPath: string, port: number): string {
149
+ const devEntry = resolve(providerPath, "dev.ts");
150
+ if (existsSync(devEntry)) {
151
+ const relativeDevEntry = relative(process.cwd(), devEntry) || "dev.ts";
152
+ const portPrefix = process.env.APIFUSE__RUNTIME__PORT
153
+ ? `APIFUSE__RUNTIME__PORT=${port} `
154
+ : "";
155
+ return `${portPrefix}bun --hot ${relativeDevEntry}`;
156
+ }
157
+ return "rerun `apifuse dev` after edits (no dev.ts entrypoint found)";
158
+ }
159
+
160
+ function shellSingleQuote(value: string): string {
161
+ return `'${value.replaceAll("'", "'\\''")}'`;
116
162
  }
117
163
 
118
164
  function createUnsupportedBrowserStub(): BrowserClient {
119
165
  return {
120
166
  engine: "playwright-stealth",
167
+ async close() {},
121
168
  async newPage() {
122
169
  throw new ProviderError(
123
170
  "Browser runtime is not enabled for this provider",
@@ -127,6 +174,33 @@ function createUnsupportedBrowserStub(): BrowserClient {
127
174
  },
128
175
  );
129
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
+ },
130
204
  };
131
205
  }
132
206
 
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { execFileSync } from "node:child_process";
4
+ import { readFileSync } from "node:fs";
4
5
  import { z } from "zod";
5
6
 
6
7
  const PACK_RESULT_SCHEMA = z.array(
@@ -28,12 +29,47 @@ if (!first) {
28
29
  }
29
30
 
30
31
  const filePaths = (first.files ?? []).map((file) => file.path);
32
+ const requiredPaths = [
33
+ "bin/apifuse.ts",
34
+ "bin/apifuse-create.ts",
35
+ "bin/apifuse-pack-smoke.ts",
36
+ "bin/apifuse-submit-check.ts",
37
+ "SUBMISSION.md",
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",
42
+ "src/cli/templates/provider/index.ts.tpl",
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",
63
+ ];
31
64
  const forbiddenMatches = filePaths.filter(
32
65
  (path) =>
33
66
  path.startsWith("src/__tests__/") ||
34
67
  path === "src/index.test.ts" ||
35
68
  path === "bin/apifuse-init.ts",
36
69
  );
70
+ const missingRequiredPaths = requiredPaths.filter(
71
+ (path) => !filePaths.includes(path),
72
+ );
37
73
 
38
74
  if (forbiddenMatches.length > 0) {
39
75
  throw new Error(
@@ -41,7 +77,91 @@ if (forbiddenMatches.length > 0) {
41
77
  );
42
78
  }
43
79
 
80
+ if (missingRequiredPaths.length > 0) {
81
+ throw new Error(
82
+ `Packed artifact is missing required public SDK files:\n${missingRequiredPaths.join("\n")}`,
83
+ );
84
+ }
85
+
86
+ const packageJsonInput: unknown = JSON.parse(
87
+ readFileSync("package.json", "utf8"),
88
+ );
89
+ const packageJson = z
90
+ .object({
91
+ dependencies: z.record(z.string(), z.string()).optional(),
92
+ devDependencies: z.record(z.string(), z.string()).optional(),
93
+ })
94
+ .parse(packageJsonInput);
95
+
96
+ if (!packageJson.dependencies?.["@clack/prompts"]) {
97
+ throw new Error(
98
+ "@clack/prompts is imported by the public create CLI and must be listed in dependencies.",
99
+ );
100
+ }
101
+
102
+ if (packageJson.devDependencies?.["@clack/prompts"]) {
103
+ throw new Error(
104
+ "@clack/prompts must not be devDependency-only because the published create CLI imports it at runtime.",
105
+ );
106
+ }
107
+
108
+ assertPublicSmokeDocs("README.md", readFileSync("README.md", "utf8"));
109
+ assertPublicSmokeDocs(
110
+ "src/cli/templates/provider/README.md.tpl",
111
+ readFileSync("src/cli/templates/provider/README.md.tpl", "utf8"),
112
+ );
113
+
44
114
  console.log(`Packed artifact OK: ${first.filename}`);
45
115
  for (const filePath of filePaths) {
46
116
  console.log(` - ${filePath}`);
47
117
  }
118
+
119
+ function assertPublicSmokeDocs(label: string, content: string): void {
120
+ if (!content.includes('"requestId":"req_local_ping"')) {
121
+ throw new Error(
122
+ `${label} must document the current provider server request envelope with requestId.`,
123
+ );
124
+ }
125
+
126
+ if (content.includes('"connection":null')) {
127
+ throw new Error(
128
+ `${label} must not tell public users to send connection:null; omit connection for no-auth operations.`,
129
+ );
130
+ }
131
+
132
+ if (!content.includes("bunx playwright install chromium")) {
133
+ throw new Error(
134
+ `${label} must include browser runtime troubleshooting for public SDK-only debugging.`,
135
+ );
136
+ }
137
+
138
+ if (!content.includes("impit")) {
139
+ throw new Error(
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.`,
147
+ );
148
+ }
149
+
150
+ if (
151
+ !content.includes('browser.engine: "playwright-stealth"') ||
152
+ !content.includes("nodriver")
153
+ ) {
154
+ throw new Error(
155
+ `${label} must clarify that TypeScript browser providers use playwright-stealth and nodriver is not the TypeScript happy path.`,
156
+ );
157
+ }
158
+
159
+ if (
160
+ label.includes("templates/provider/README.md.tpl") &&
161
+ !content.includes("bun run record -- --operation <operation>")
162
+ ) {
163
+ throw new Error(
164
+ `${label} must document fixture recording through a generated package script, not a shell-global apifuse command.`,
165
+ );
166
+ }
167
+ }