@apifuse/provider-sdk 2.1.0-beta.5 → 2.1.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.1.0-beta.6
4
+
5
+ - Public repository clean-import release for `APIFuseHQ/provider-sdk`.
6
+ - Preserves the monorepo SDK exports required by ApiFuse provider registry cutover, including `./contract` and provider i18n helpers.
7
+
8
+
3
9
  ## 2.1.0-beta.5
4
10
 
5
11
  - Republish the bounty workspace DX hardening that accepts generated readonly metadata and factored `defineOperation()` maps during standalone TypeScript checks.
package/README.md CHANGED
@@ -216,11 +216,11 @@ Operations declare non-functional metadata via `annotations`:
216
216
  apifuse create <name>
217
217
  apifuse dev [path]
218
218
  apifuse check [path]
219
- apifuse record providers/airkorea --operation realtime --params '{"stationName":"종로구"}'
219
+ apifuse record providers/korea-air-quality --operation realtime --params '{"stationName":"종로구"}'
220
220
  apifuse test [path]
221
221
  apifuse submit-check [path] --tier bronze --markdown submission-report.md
222
222
  apifuse bounty-check [path]
223
- apifuse perf providers/airkorea --operation realtime --params '{"stationName":"종로구"}'
223
+ apifuse perf providers/korea-air-quality --operation realtime --params '{"stationName":"종로구"}'
224
224
  ```
225
225
 
226
226
  `apifuse record` is for real upstream-backed operations that declare
package/SUBMISSION.md CHANGED
@@ -48,8 +48,9 @@ Fix all blockers before submitting:
48
48
  - Missing `healthCheck` or `healthCheckUnsupported` on any Operation.
49
49
  - Credential-backed auth mode without declared credential keys.
50
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.
51
52
 
52
- 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.
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`.
53
54
 
54
55
  ## Safe local smoke evidence
55
56
 
@@ -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
  );
@@ -9,16 +9,18 @@ import {
9
9
  createEnvContext,
10
10
  createHttpClient,
11
11
  createProviderCache,
12
+ createProviderChoiceContext,
12
13
  createStealthClient,
13
14
  createSttClientFromEnv,
15
+ PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
14
16
  ProviderError,
15
17
  } from "../src";
16
- import { createUnsupportedProviderRuntimeState } from "../src/runtime/state";
18
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state";
17
19
  import { createTraceContext } from "../src/runtime/trace";
18
20
  import type { BrowserClient, ProviderContext } from "../src/types";
19
21
 
20
22
  const HELP_TEXT = `Usage: apifuse dev [path]
21
- Example: apifuse dev providers/airkorea
23
+ Example: apifuse dev providers/korea-air-quality
22
24
  Default: apifuse dev .`;
23
25
 
24
26
  export async function main() {
@@ -76,22 +78,35 @@ export async function main() {
76
78
  export function createProviderContext(provider: ProviderDefinition): {
77
79
  ctx: ProviderContext;
78
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();
79
87
  const ctx: ProviderContext = {
80
- env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
81
- credential: createCredentialContext(),
88
+ env,
89
+ credential,
82
90
  auth: createUnsupportedAuthStub(),
83
91
  browser:
84
92
  provider.runtime === "browser"
85
93
  ? createBrowserClient({
94
+ allowedHosts: provider.allowedHosts,
86
95
  engine: provider.browser?.engine ?? "playwright-stealth",
87
96
  })
88
97
  : createUnsupportedBrowserStub(),
89
98
  http: createHttpClient(),
90
99
  cache: createProviderCache({ providerId: provider.id }),
91
- state: createUnsupportedProviderRuntimeState(),
100
+ state,
92
101
  trace: createTraceContext(),
93
102
  stealth: createStealthClient("http://localhost"),
94
103
  stt: createSttClientFromEnv(provider.stt),
104
+ choice: createProviderChoiceContext({
105
+ providerId: provider.id,
106
+ env,
107
+ credential,
108
+ state,
109
+ }),
95
110
  };
96
111
 
97
112
  return { ctx };
@@ -149,6 +164,7 @@ function shellSingleQuote(value: string): string {
149
164
  function createUnsupportedBrowserStub(): BrowserClient {
150
165
  return {
151
166
  engine: "playwright-stealth",
167
+ async close() {},
152
168
  async newPage() {
153
169
  throw new ProviderError(
154
170
  "Browser runtime is not enabled for this provider",
@@ -158,6 +174,33 @@ function createUnsupportedBrowserStub(): BrowserClient {
158
174
  },
159
175
  );
160
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
+ },
161
204
  };
162
205
  }
163
206
 
@@ -10,6 +10,7 @@ import {
10
10
  type ApiFuseConfig,
11
11
  createBypassProviderCache,
12
12
  createHttpClient,
13
+ createProviderChoiceContext,
13
14
  createStealthClient,
14
15
  createSttClientFromEnv,
15
16
  executeOperation,
@@ -29,6 +30,7 @@ import {
29
30
  groupSpansByName,
30
31
  type PerfStats,
31
32
  } from "../src/runtime/perf";
33
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state";
32
34
  import {
33
35
  createTraceContext,
34
36
  resolveTraceContextOptions,
@@ -96,7 +98,7 @@ Options:
96
98
  --help, -h show this help
97
99
 
98
100
  Example:
99
- apifuse perf providers/airkorea --operation realtime --params '{"stationName":"jongno"}' --runs 5`;
101
+ apifuse perf providers/korea-air-quality --operation realtime --params '{"stationName":"jongno"}' --runs 5`;
100
102
 
101
103
  export async function main() {
102
104
  try {
@@ -688,24 +690,36 @@ function createBaseContext(options: {
688
690
  upstream,
689
691
  });
690
692
 
693
+ const env = {
694
+ get: (key: string) => process.env[key],
695
+ };
696
+ const credential = {
697
+ mode: "none" as const,
698
+ get: () => undefined,
699
+ getAll: () => ({}),
700
+ getAccessToken: () => undefined,
701
+ getScopes: () => [],
702
+ };
703
+ const state = createMemoryProviderRuntimeState();
691
704
  return {
692
- env: {
693
- get: (key: string) => process.env[key],
694
- },
695
- credential: {
696
- mode: "none",
697
- get: () => undefined,
698
- getAll: () => ({}),
699
- getAccessToken: () => undefined,
700
- getScopes: () => [],
701
- },
705
+ env,
706
+ credential,
707
+ request: { headers: {} },
702
708
  http,
703
709
  cache: createBypassProviderCache({ providerId: options.provider.id }),
710
+ state,
704
711
  stealth,
705
712
  browser: createBrowserStub(),
706
713
  trace: options.traceContext,
707
714
  auth: createAuthStub(),
708
715
  stt: createSttClientFromEnv(options.provider.stt),
716
+ choice: createProviderChoiceContext({
717
+ providerId: options.provider.id,
718
+ env,
719
+ request: { headers: {} },
720
+ credential,
721
+ state,
722
+ }),
709
723
  };
710
724
  }
711
725
 
@@ -765,6 +779,7 @@ function createFixtureStealthClient(rawText: string): StealthClient {
765
779
  function createBrowserStub(): BrowserClient {
766
780
  return {
767
781
  engine: "playwright-stealth",
782
+ async close() {},
768
783
  async newPage() {
769
784
  throw new ProviderError(
770
785
  "Browser runtime is not supported by apifuse perf yet.",
@@ -773,6 +788,30 @@ function createBrowserStub(): BrowserClient {
773
788
  },
774
789
  );
775
790
  },
791
+ async rawPage() {
792
+ throw new ProviderError(
793
+ "Browser runtime is not supported by apifuse perf yet.",
794
+ {
795
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
796
+ },
797
+ );
798
+ },
799
+ async withIsolatedContext() {
800
+ throw new ProviderError(
801
+ "Browser runtime is not supported by apifuse perf yet.",
802
+ {
803
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
804
+ },
805
+ );
806
+ },
807
+ async solveChallenge() {
808
+ throw new ProviderError(
809
+ "Browser runtime is not supported by apifuse perf yet.",
810
+ {
811
+ code: "BROWSER_RUNTIME_UNSUPPORTED",
812
+ },
813
+ );
814
+ },
776
815
  };
777
816
  }
778
817
 
@@ -9,6 +9,7 @@ import { pathToFileURL } from "node:url";
9
9
  import {
10
10
  createBypassProviderCache,
11
11
  createHttpClient,
12
+ createProviderChoiceContext,
12
13
  createStealthClient,
13
14
  createSttClientFromEnv,
14
15
  executeOperation,
@@ -20,6 +21,7 @@ import {
20
21
  TransportError,
21
22
  ValidationError,
22
23
  } from "../src";
24
+ import { createMemoryProviderRuntimeState } from "../src/runtime/state";
23
25
 
24
26
  type CliArgs = {
25
27
  append: boolean;
@@ -46,7 +48,7 @@ Options:
46
48
  --help, -h show this help
47
49
 
48
50
  Example:
49
- apifuse record providers/airkorea --operation realtime --params '{"stationName":"jongno"}'`;
51
+ apifuse record providers/korea-air-quality --operation realtime --params '{"stationName":"jongno"}'`;
50
52
 
51
53
  export async function main() {
52
54
  try {
@@ -351,25 +353,40 @@ function createCaptureContext(provider: ProviderRuntime, baseUrl: string) {
351
353
  },
352
354
  );
353
355
 
356
+ const env = {
357
+ get: (key: string) => process.env[key],
358
+ };
359
+ const credential = {
360
+ mode: "none" as const,
361
+ get: () => undefined,
362
+ getAll: () => ({}),
363
+ getAccessToken: () => undefined,
364
+ getScopes: () => [],
365
+ };
366
+ const state = createMemoryProviderRuntimeState();
354
367
  const ctx: ProviderContext = {
355
- env: {
356
- get: (key) => process.env[key],
357
- },
358
- credential: {
359
- mode: "none",
360
- get: () => undefined,
361
- getAll: () => ({}),
362
- getAccessToken: () => undefined,
363
- getScopes: () => [],
364
- },
368
+ env,
369
+ credential,
370
+ request: { headers: {} },
365
371
  http,
366
372
  cache: createBypassProviderCache({ providerId: provider.id }),
373
+ state,
367
374
  stealth,
368
375
  browser: {
369
376
  engine: "playwright-stealth",
377
+ close: async () => {},
370
378
  newPage: async () => {
371
379
  throw new Error("Browser client is not available in apifuse record.");
372
380
  },
381
+ rawPage: async () => {
382
+ throw new Error("Browser client is not available in apifuse record.");
383
+ },
384
+ withIsolatedContext: async () => {
385
+ throw new Error("Browser client is not available in apifuse record.");
386
+ },
387
+ solveChallenge: async () => {
388
+ throw new Error("Browser client is not available in apifuse record.");
389
+ },
373
390
  },
374
391
  trace: {
375
392
  span: async (_name, fn) => fn(),
@@ -380,6 +397,13 @@ function createCaptureContext(provider: ProviderRuntime, baseUrl: string) {
380
397
  },
381
398
  },
382
399
  stt: createSttClientFromEnv(provider.stt),
400
+ choice: createProviderChoiceContext({
401
+ providerId: provider.id,
402
+ env,
403
+ request: { headers: {} },
404
+ credential,
405
+ state,
406
+ }),
383
407
  };
384
408
 
385
409
  return {