@apifuse/provider-sdk 2.1.0-beta.3 → 2.1.0-beta.4
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/AUTHORING.md +163 -8
- package/CHANGELOG.md +8 -1
- package/README.md +17 -16
- package/SUBMISSION.md +4 -4
- package/bin/apifuse-dev.ts +12 -5
- package/bin/apifuse-pack-check.ts +9 -2
- package/bin/apifuse-pack-smoke.ts +127 -6
- package/bin/apifuse-perf.ts +19 -15
- package/bin/apifuse-record.ts +41 -53
- package/bin/apifuse-submit-check.ts +179 -7
- package/bin/apifuse.ts +1 -1
- package/package.json +17 -8
- package/src/choice-token.ts +164 -0
- package/src/cli/commands.ts +1 -3
- package/src/cli/create.ts +159 -50
- package/src/cli/templates/provider/README.md.tpl +24 -7
- package/src/cli/templates/provider/dev.ts.tpl +1 -1
- package/src/cli/templates/provider/domain/README.md.tpl +3 -0
- package/src/cli/templates/provider/index.ts.tpl +5 -47
- package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/src/cli/templates/provider/meta.ts.tpl +7 -0
- package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/src/cli/templates/provider/operations/ping.ts.tpl +23 -0
- package/src/cli/templates/provider/schemas/ping.ts.tpl +16 -0
- package/src/cli/templates/provider/start.ts.tpl +1 -1
- package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/src/config/loader.ts +1206 -9
- package/src/define.ts +1618 -104
- package/src/errors.ts +12 -0
- package/src/i18n/catalog.ts +121 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/keys.ts +64 -0
- package/src/index.ts +149 -8
- package/src/lint.ts +297 -42
- package/src/observability.ts +41 -0
- package/src/provider.ts +60 -3
- package/src/public-schema-field-lint.ts +237 -0
- package/src/runtime/auth-flow.ts +7 -0
- package/src/runtime/browser.ts +77 -21
- package/src/runtime/cache.ts +582 -0
- package/src/runtime/executor.ts +13 -1
- package/src/runtime/http.ts +939 -195
- package/src/runtime/insights.ts +11 -11
- package/src/runtime/instrumentation.ts +12 -4
- package/src/runtime/key-derivation.ts +1 -1
- package/src/runtime/keyring.ts +4 -3
- package/src/runtime/proxy-errors.ts +132 -0
- package/src/runtime/proxy-telemetry.ts +253 -0
- package/src/runtime/request-options.ts +66 -0
- package/src/runtime/state.ts +76 -0
- package/src/runtime/stealth.ts +1145 -0
- package/src/runtime/stt.ts +629 -0
- package/src/schema.ts +363 -1
- package/src/server/serve.ts +816 -58
- package/src/server/types.ts +35 -0
- package/src/stream.ts +210 -0
- package/src/testing/run.ts +17 -4
- package/src/types.ts +869 -50
- package/src/runtime/tls.ts +0 -434
- package/src/types/playwright-stealth.d.ts +0 -9
package/bin/apifuse-perf.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { pathToFileURL } from "node:url";
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
type ApiFuseConfig,
|
|
11
|
+
createBypassProviderCache,
|
|
11
12
|
createHttpClient,
|
|
12
|
-
|
|
13
|
+
createStealthClient,
|
|
14
|
+
createSttClientFromEnv,
|
|
13
15
|
executeOperation,
|
|
14
16
|
getProviderBaseUrl,
|
|
15
17
|
type HttpClient,
|
|
@@ -18,8 +20,8 @@ import {
|
|
|
18
20
|
type ProviderDefinition,
|
|
19
21
|
ProviderError,
|
|
20
22
|
type Span,
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
+
type StealthClient,
|
|
24
|
+
type StealthResponse,
|
|
23
25
|
wrapWithInstrumentation,
|
|
24
26
|
} from "../src";
|
|
25
27
|
import {
|
|
@@ -440,12 +442,12 @@ async function loadFixtureReplay(
|
|
|
440
442
|
}
|
|
441
443
|
|
|
442
444
|
function assertProxyConfigured(config: ApiFuseConfig): void {
|
|
443
|
-
if (config.proxy?.url || process.env.
|
|
445
|
+
if (config.proxy?.url || process.env.APIFUSE__PROXY__URL) {
|
|
444
446
|
return;
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
throw new Error(
|
|
448
|
-
"--compare-proxy requires a proxy URL in apifuse.config.ts or
|
|
450
|
+
"--compare-proxy requires a proxy URL in apifuse.config.ts or APIFUSE__PROXY__URL.",
|
|
449
451
|
);
|
|
450
452
|
}
|
|
451
453
|
|
|
@@ -637,10 +639,10 @@ function createBaseContext(options: {
|
|
|
637
639
|
apifuseConfig,
|
|
638
640
|
upstream,
|
|
639
641
|
});
|
|
640
|
-
const
|
|
642
|
+
const stealth =
|
|
641
643
|
options.forceFixtureReplay && options.fixtureReplay
|
|
642
|
-
?
|
|
643
|
-
:
|
|
644
|
+
? createFixtureStealthClient(options.fixtureReplay.rawText)
|
|
645
|
+
: createStealthClient(getProviderBaseUrl(options.provider), {
|
|
644
646
|
apifuseConfig,
|
|
645
647
|
upstream,
|
|
646
648
|
});
|
|
@@ -657,10 +659,12 @@ function createBaseContext(options: {
|
|
|
657
659
|
getScopes: () => [],
|
|
658
660
|
},
|
|
659
661
|
http,
|
|
660
|
-
|
|
662
|
+
cache: createBypassProviderCache({ providerId: options.provider.id }),
|
|
663
|
+
stealth,
|
|
661
664
|
browser: createBrowserStub(),
|
|
662
665
|
trace: options.traceContext,
|
|
663
666
|
auth: createAuthStub(),
|
|
667
|
+
stt: createSttClientFromEnv(options.provider.stt),
|
|
664
668
|
};
|
|
665
669
|
}
|
|
666
670
|
|
|
@@ -695,8 +699,8 @@ function createFixtureResponse(raw: unknown) {
|
|
|
695
699
|
};
|
|
696
700
|
}
|
|
697
701
|
|
|
698
|
-
function
|
|
699
|
-
const createResponse = async (): Promise<
|
|
702
|
+
function createFixtureStealthClient(rawText: string): StealthClient {
|
|
703
|
+
const createResponse = async (): Promise<StealthResponse> => ({
|
|
700
704
|
status: 200,
|
|
701
705
|
ok: true,
|
|
702
706
|
headers: { "content-type": "application/json" },
|
|
@@ -738,7 +742,7 @@ function buildInsights(
|
|
|
738
742
|
): string[] {
|
|
739
743
|
const insights: string[] = [];
|
|
740
744
|
const allSpans = runs.flatMap((run) => run.spans);
|
|
741
|
-
const
|
|
745
|
+
const stealthSpans = allSpans.filter((span) => span.name === "stealth.fetch");
|
|
742
746
|
const dnsSpans = allSpans.filter((span) => span.name === "dns");
|
|
743
747
|
const transform = breakdown.find(
|
|
744
748
|
(entry) => entry.name === "transformResponse",
|
|
@@ -746,7 +750,7 @@ function buildInsights(
|
|
|
746
750
|
const responseSizes = allSpans
|
|
747
751
|
.map((span) => span.attributes.response_size)
|
|
748
752
|
.filter((value): value is number => typeof value === "number");
|
|
749
|
-
const reuseFlags =
|
|
753
|
+
const reuseFlags = stealthSpans
|
|
750
754
|
.map((span) => span.attributes.connection_reused)
|
|
751
755
|
.filter((value): value is boolean => typeof value === "boolean");
|
|
752
756
|
|
|
@@ -756,8 +760,8 @@ function buildInsights(
|
|
|
756
760
|
);
|
|
757
761
|
insights.push(
|
|
758
762
|
reusePercent >= 80
|
|
759
|
-
? `✓
|
|
760
|
-
: `⚠
|
|
763
|
+
? `✓ Stealth connection reuse: ${reusePercent}% (good)`
|
|
764
|
+
: `⚠ Stealth connection reuse: ${reusePercent}% — consider session reuse`,
|
|
761
765
|
);
|
|
762
766
|
}
|
|
763
767
|
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -7,13 +7,15 @@ import { basename, dirname, relative, resolve } from "node:path";
|
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
+
createBypassProviderCache,
|
|
10
11
|
createHttpClient,
|
|
11
|
-
|
|
12
|
+
createStealthClient,
|
|
13
|
+
createSttClientFromEnv,
|
|
12
14
|
executeOperation,
|
|
13
15
|
type HttpClient,
|
|
14
16
|
type ProviderContext,
|
|
15
17
|
type ProviderDefinition,
|
|
16
|
-
type
|
|
18
|
+
type StealthClient,
|
|
17
19
|
} from "../src";
|
|
18
20
|
|
|
19
21
|
type CliArgs = {
|
|
@@ -37,6 +39,7 @@ export async function main() {
|
|
|
37
39
|
const parsedParams = parseParams(operation, args.params);
|
|
38
40
|
|
|
39
41
|
const capture = createCaptureContext(
|
|
42
|
+
provider,
|
|
40
43
|
resolveOperationBaseUrl(provider, operationName),
|
|
41
44
|
);
|
|
42
45
|
|
|
@@ -269,15 +272,18 @@ function resolveOperationBaseUrl(
|
|
|
269
272
|
return baseUrl;
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
function createCaptureContext(baseUrl: string) {
|
|
275
|
+
function createCaptureContext(provider: ProviderRuntime, baseUrl: string) {
|
|
273
276
|
let capturedRaw: unknown;
|
|
274
277
|
|
|
275
278
|
const http = proxyHttpClient(createHttpClient(baseUrl), (response) => {
|
|
276
279
|
capturedRaw = response.data;
|
|
277
280
|
});
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
const stealth = proxyStealthClient(
|
|
282
|
+
createStealthClient(baseUrl),
|
|
283
|
+
(response) => {
|
|
284
|
+
capturedRaw = normalizeCapturedStealthResponse(response);
|
|
285
|
+
},
|
|
286
|
+
);
|
|
281
287
|
|
|
282
288
|
const ctx: ProviderContext = {
|
|
283
289
|
env: {
|
|
@@ -291,7 +297,8 @@ function createCaptureContext(baseUrl: string) {
|
|
|
291
297
|
getScopes: () => [],
|
|
292
298
|
},
|
|
293
299
|
http,
|
|
294
|
-
|
|
300
|
+
cache: createBypassProviderCache({ providerId: provider.id }),
|
|
301
|
+
stealth,
|
|
295
302
|
browser: {
|
|
296
303
|
engine: "playwright-stealth",
|
|
297
304
|
newPage: async () => {
|
|
@@ -306,6 +313,7 @@ function createCaptureContext(baseUrl: string) {
|
|
|
306
313
|
throw new Error("Auth prompts are not available in apifuse record.");
|
|
307
314
|
},
|
|
308
315
|
},
|
|
316
|
+
stt: createSttClientFromEnv(provider.stt),
|
|
309
317
|
};
|
|
310
318
|
|
|
311
319
|
return {
|
|
@@ -335,59 +343,39 @@ function proxyHttpClient(
|
|
|
335
343
|
}) as HttpClient;
|
|
336
344
|
}
|
|
337
345
|
|
|
338
|
-
|
|
339
|
-
client: TlsClient,
|
|
340
|
-
onResponse: (response: Awaited<ReturnType<TlsClient["fetch"]>>) => void,
|
|
341
|
-
): TlsClient {
|
|
342
|
-
return new Proxy(client, {
|
|
343
|
-
get(target, prop, receiver) {
|
|
344
|
-
const value = Reflect.get(target, prop, receiver);
|
|
345
|
-
|
|
346
|
-
if (prop === "fetch" && typeof value === "function") {
|
|
347
|
-
return async (...args: unknown[]) => {
|
|
348
|
-
const response = await value.apply(target, args);
|
|
349
|
-
onResponse(response);
|
|
350
|
-
return response;
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (prop === "createSession" && typeof value === "function") {
|
|
355
|
-
return (...args: unknown[]) => {
|
|
356
|
-
const session = value.apply(target, args) as ReturnType<
|
|
357
|
-
TlsClient["createSession"]
|
|
358
|
-
>;
|
|
359
|
-
return proxyTlsSession(session, onResponse);
|
|
360
|
-
};
|
|
361
|
-
}
|
|
346
|
+
type StealthSession = ReturnType<StealthClient["createSession"]>;
|
|
362
347
|
|
|
363
|
-
|
|
348
|
+
function proxyStealthClient(
|
|
349
|
+
client: StealthClient,
|
|
350
|
+
onResponse: (response: Awaited<ReturnType<StealthClient["fetch"]>>) => void,
|
|
351
|
+
): StealthClient {
|
|
352
|
+
return {
|
|
353
|
+
fetch: async (...args: Parameters<StealthClient["fetch"]>) => {
|
|
354
|
+
const response = await client.fetch(...args);
|
|
355
|
+
onResponse(response);
|
|
356
|
+
return response;
|
|
364
357
|
},
|
|
365
|
-
|
|
358
|
+
createSession: (...args: Parameters<StealthClient["createSession"]>) =>
|
|
359
|
+
proxyStealthSession(client.createSession(...args), onResponse),
|
|
360
|
+
};
|
|
366
361
|
}
|
|
367
362
|
|
|
368
|
-
function
|
|
369
|
-
session:
|
|
370
|
-
onResponse: (response: Awaited<ReturnType<
|
|
371
|
-
) {
|
|
372
|
-
return
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return async (...args: unknown[]) => {
|
|
378
|
-
const response = await value.apply(target, args);
|
|
379
|
-
onResponse(response);
|
|
380
|
-
return response;
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return value;
|
|
363
|
+
function proxyStealthSession(
|
|
364
|
+
session: StealthSession,
|
|
365
|
+
onResponse: (response: Awaited<ReturnType<StealthClient["fetch"]>>) => void,
|
|
366
|
+
): StealthSession {
|
|
367
|
+
return {
|
|
368
|
+
fetch: async (...args: Parameters<StealthSession["fetch"]>) => {
|
|
369
|
+
const response = await session.fetch(...args);
|
|
370
|
+
onResponse(response);
|
|
371
|
+
return response;
|
|
385
372
|
},
|
|
386
|
-
|
|
373
|
+
close: () => session.close(),
|
|
374
|
+
};
|
|
387
375
|
}
|
|
388
376
|
|
|
389
|
-
function
|
|
390
|
-
response: Awaited<ReturnType<
|
|
377
|
+
function normalizeCapturedStealthResponse(
|
|
378
|
+
response: Awaited<ReturnType<StealthClient["fetch"]>>,
|
|
391
379
|
) {
|
|
392
380
|
try {
|
|
393
381
|
return JSON.parse(response.body);
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { writeFile } from "node:fs/promises";
|
|
5
|
-
import { basename, dirname, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
8
10
|
import packageJson from "../package.json";
|
|
9
11
|
import type { ProviderDefinition } from "../src";
|
|
12
|
+
import {
|
|
13
|
+
loadProviderLocaleCatalogs,
|
|
14
|
+
type ProviderLocale,
|
|
15
|
+
validateProviderLocaleCatalogs,
|
|
16
|
+
} from "../src/i18n";
|
|
17
|
+
import { APIFUSE_DESCRIPTION_KEY_META_KEY } from "../src/schema";
|
|
10
18
|
import { type CheckResult, runChecks } from "./apifuse-check";
|
|
11
19
|
|
|
12
20
|
const TIERS = ["bronze", "silver", "gold", "diamond"] as const;
|
|
@@ -77,6 +85,11 @@ const CATEGORY_MAX_POINTS = {
|
|
|
77
85
|
docs: 10,
|
|
78
86
|
} as const;
|
|
79
87
|
|
|
88
|
+
const REQUIRED_PUBLIC_PROVIDER_LOCALES = [
|
|
89
|
+
"en",
|
|
90
|
+
"ko",
|
|
91
|
+
] as const satisfies readonly ProviderLocale[];
|
|
92
|
+
|
|
80
93
|
const HELP_TEXT = `Usage: apifuse submit-check [path] [--tier bronze|silver|gold|diamond] [--json] [--markdown <path>] [--smoke-note <text>]
|
|
81
94
|
Alias: apifuse bounty-check [path]
|
|
82
95
|
Default: apifuse submit-check .`;
|
|
@@ -220,6 +233,7 @@ export async function buildSubmitCheckReport(
|
|
|
220
233
|
checks.push(...scoreBaseChecks(baseChecks));
|
|
221
234
|
|
|
222
235
|
if (provider) {
|
|
236
|
+
checks.push(scoreLocaleCatalog(providerRoot, provider));
|
|
223
237
|
checks.push(scoreOperationMetadata(provider));
|
|
224
238
|
checks.push(scoreFixtureCoverage(provider));
|
|
225
239
|
checks.push(scoreHealthCoverage(provider));
|
|
@@ -322,12 +336,170 @@ function scoreBaseChecks(results: CheckResult[]): SubmitCheck[] {
|
|
|
322
336
|
];
|
|
323
337
|
}
|
|
324
338
|
|
|
339
|
+
function scoreLocaleCatalog(
|
|
340
|
+
providerRoot: string,
|
|
341
|
+
provider: ProviderDefinition,
|
|
342
|
+
): SubmitCheck {
|
|
343
|
+
const requiredKeys = collectProviderRequiredLocaleKeys(provider);
|
|
344
|
+
if (requiredKeys.length === 0) {
|
|
345
|
+
return pass(
|
|
346
|
+
"locale-catalog",
|
|
347
|
+
"operations",
|
|
348
|
+
"No key-owned provider metadata or operation metadata requires locale catalog validation.",
|
|
349
|
+
0,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const availableLocales = REQUIRED_PUBLIC_PROVIDER_LOCALES.filter((locale) =>
|
|
355
|
+
existsSync(join(providerRoot, "locales", `${locale}.json`)),
|
|
356
|
+
);
|
|
357
|
+
const catalogs = loadProviderLocaleCatalogs({
|
|
358
|
+
providerDir: providerRoot,
|
|
359
|
+
locales: availableLocales,
|
|
360
|
+
});
|
|
361
|
+
const validation = validateProviderLocaleCatalogs({
|
|
362
|
+
catalogs,
|
|
363
|
+
requiredLocales: REQUIRED_PUBLIC_PROVIDER_LOCALES,
|
|
364
|
+
requiredKeys,
|
|
365
|
+
});
|
|
366
|
+
if (!validation.ok) {
|
|
367
|
+
return blocker(
|
|
368
|
+
"locale-catalog",
|
|
369
|
+
"operations",
|
|
370
|
+
"Provider locale catalog is missing required public-provider copy.",
|
|
371
|
+
"Add provider-local locales/en.json and locales/ko.json values for every provider metadata key, operation descriptionKey, and .describeKey() or describeKey() schema field.",
|
|
372
|
+
0,
|
|
373
|
+
validation.issues.map(
|
|
374
|
+
(issue) => `${issue.locale}:${issue.key}: ${issue.message}`,
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
380
|
+
return blocker(
|
|
381
|
+
"locale-catalog",
|
|
382
|
+
"operations",
|
|
383
|
+
"Provider locale catalog is missing required public-provider copy.",
|
|
384
|
+
"Add provider-local locales/en.json and locales/ko.json values for every provider metadata key, operation descriptionKey, and .describeKey() or describeKey() schema field.",
|
|
385
|
+
0,
|
|
386
|
+
[`*:*: ${message}`],
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return pass(
|
|
391
|
+
"locale-catalog",
|
|
392
|
+
"operations",
|
|
393
|
+
"Required provider and operation locale keys resolve in locales/en.json and locales/ko.json.",
|
|
394
|
+
0,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function collectProviderRequiredLocaleKeys(
|
|
399
|
+
provider: ProviderDefinition,
|
|
400
|
+
): string[] {
|
|
401
|
+
const keys = new Set<string>();
|
|
402
|
+
|
|
403
|
+
addLocaleKeys(keys, [
|
|
404
|
+
provider.meta.descriptionKey,
|
|
405
|
+
provider.meta.docTitleKey,
|
|
406
|
+
provider.meta.docDescriptionKey,
|
|
407
|
+
provider.meta.docSummaryKey,
|
|
408
|
+
provider.meta.docMarkdownKey,
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
const publicProfile = provider.meta.publicProfile;
|
|
412
|
+
if (publicProfile) {
|
|
413
|
+
addLocaleKeys(keys, [
|
|
414
|
+
publicProfile.displayNameKey,
|
|
415
|
+
publicProfile.shortDescriptionKey,
|
|
416
|
+
publicProfile.longDescriptionKey,
|
|
417
|
+
publicProfile.setupSummaryKey,
|
|
418
|
+
...(publicProfile.capabilityKeys ?? []),
|
|
419
|
+
...(publicProfile.examplePromptKeys ?? []),
|
|
420
|
+
...(publicProfile.requirementKeys ?? []),
|
|
421
|
+
...(publicProfile.limitationKeys ?? []),
|
|
422
|
+
]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
for (const operation of Object.values(provider.operations)) {
|
|
426
|
+
addLocaleKeys(keys, [
|
|
427
|
+
operation.descriptionKey,
|
|
428
|
+
operation.docs?.titleKey,
|
|
429
|
+
operation.docs?.descriptionKey,
|
|
430
|
+
operation.docs?.summaryKey,
|
|
431
|
+
operation.docs?.markdownKey,
|
|
432
|
+
...(operation.whenToUseKeys ?? []),
|
|
433
|
+
...(operation.whenNotToUseKeys ?? []),
|
|
434
|
+
...collectSchemaDescriptionKeys(operation.input),
|
|
435
|
+
...collectSchemaDescriptionKeys(operation.output),
|
|
436
|
+
]);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return Array.from(keys);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function addLocaleKeys(keys: Set<string>, values: readonly unknown[]): void {
|
|
443
|
+
for (const key of values) {
|
|
444
|
+
if (typeof key === "string" && key.length > 0) {
|
|
445
|
+
keys.add(key);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function collectSchemaDescriptionKeys(schema: unknown): string[] {
|
|
451
|
+
if (!(schema instanceof z.ZodType)) {
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
const jsonSchema = z.toJSONSchema(schema);
|
|
455
|
+
if (!isRecord(jsonSchema)) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
const keys: string[] = [];
|
|
459
|
+
collectJsonSchemaDescriptionKeys(jsonSchema, keys);
|
|
460
|
+
return keys;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function collectJsonSchemaDescriptionKeys(
|
|
464
|
+
schema: Record<string, unknown>,
|
|
465
|
+
keys: string[],
|
|
466
|
+
): void {
|
|
467
|
+
const descriptionKey = schema[APIFUSE_DESCRIPTION_KEY_META_KEY];
|
|
468
|
+
if (typeof descriptionKey === "string" && descriptionKey.length > 0) {
|
|
469
|
+
keys.push(descriptionKey);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const value of Object.values(schema)) {
|
|
473
|
+
if (isRecord(value)) {
|
|
474
|
+
collectJsonSchemaDescriptionKeys(value, keys);
|
|
475
|
+
} else if (Array.isArray(value)) {
|
|
476
|
+
for (const item of value) {
|
|
477
|
+
if (isRecord(item)) {
|
|
478
|
+
collectJsonSchemaDescriptionKeys(item, keys);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
486
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
487
|
+
}
|
|
488
|
+
|
|
325
489
|
function scoreOperationMetadata(provider: ProviderDefinition): SubmitCheck {
|
|
326
490
|
const operations = Object.entries(provider.operations);
|
|
327
491
|
const weakDescriptions = operations
|
|
328
|
-
.filter(
|
|
329
|
-
|
|
330
|
-
|
|
492
|
+
.filter(([, operation]) => {
|
|
493
|
+
// Hard-cut providers move operation copy into locale catalogs via
|
|
494
|
+
// descriptionKey instead of raw inline prose; the resolved text length
|
|
495
|
+
// is enforced at registry catalog-build time, matching how lintOperation
|
|
496
|
+
// skips the raw-description min-length rule when a descriptionKey is set.
|
|
497
|
+
const hasDescriptionKey =
|
|
498
|
+
typeof operation.descriptionKey === "string" &&
|
|
499
|
+
operation.descriptionKey.length > 0;
|
|
500
|
+
if (hasDescriptionKey) return false;
|
|
501
|
+
return true;
|
|
502
|
+
})
|
|
331
503
|
.map(([operationId]) => operationId);
|
|
332
504
|
const missingAnnotations = operations
|
|
333
505
|
.filter(([, operation]) => !operation.annotations)
|
|
@@ -520,7 +692,7 @@ function scoreSmokeEvidence(smokeNote: string | undefined): SubmitCheck {
|
|
|
520
692
|
maxPoints: CATEGORY_MAX_POINTS.smoke,
|
|
521
693
|
message: "No local smoke evidence was provided.",
|
|
522
694
|
remediation:
|
|
523
|
-
"Start `bun run dev`, call `/health` and at least one `POST /v1/{operation}`, then rerun with `--smoke-note` or paste notes in the
|
|
695
|
+
"Start `bun run dev`, call `/health` and at least one `POST /v1/{operation}`, then rerun with `--smoke-note` or paste notes in the assigned workspace PR.",
|
|
524
696
|
};
|
|
525
697
|
}
|
|
526
698
|
|
|
@@ -784,7 +956,7 @@ function blocker(
|
|
|
784
956
|
|
|
785
957
|
export function renderText(report: SubmitCheckReport): string {
|
|
786
958
|
const lines = [
|
|
787
|
-
`
|
|
959
|
+
`APIFuse Provider Submission Score: ${report.score.total} / ${report.score.max}`,
|
|
788
960
|
`Verdict: ${report.score.verdict.toUpperCase()}`,
|
|
789
961
|
`Provider: ${report.provider.id}@${report.provider.version} (${report.provider.runtime}, auth: ${report.provider.authMode})`,
|
|
790
962
|
`Blockers: ${report.summary.blockers} Warnings: ${report.summary.warnings} Passed: ${report.summary.passed}`,
|
|
@@ -811,7 +983,7 @@ export function renderText(report: SubmitCheckReport): string {
|
|
|
811
983
|
|
|
812
984
|
export function renderMarkdown(report: SubmitCheckReport): string {
|
|
813
985
|
const lines = [
|
|
814
|
-
"#
|
|
986
|
+
"# APIFuse Provider Submission Report",
|
|
815
987
|
"",
|
|
816
988
|
`- **Provider**: ${report.provider.id}@${report.provider.version}`,
|
|
817
989
|
`- **SDK**: ${report.provider.sdkVersion}`,
|
package/bin/apifuse.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apifuse/provider-sdk",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "APIFuse Provider SDK — Build providers with zero architectural constraints",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"types": "./src/index.ts",
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"default": "./src/testing/index.ts",
|
|
54
54
|
"import": "./src/testing/index.ts",
|
|
55
55
|
"types": "./src/testing/index.ts"
|
|
56
|
+
},
|
|
57
|
+
"./create": {
|
|
58
|
+
"default": "./src/cli/create.ts",
|
|
59
|
+
"import": "./src/cli/create.ts",
|
|
60
|
+
"types": "./src/cli/create.ts"
|
|
56
61
|
}
|
|
57
62
|
},
|
|
58
63
|
"scripts": {
|
|
@@ -66,20 +71,24 @@
|
|
|
66
71
|
"pack:smoke": "bun bin/apifuse-pack-smoke.ts"
|
|
67
72
|
},
|
|
68
73
|
"devDependencies": {
|
|
69
|
-
"@biomejs/biome": "^2.
|
|
74
|
+
"@biomejs/biome": "^2.5.0",
|
|
70
75
|
"@types/bun": "latest",
|
|
71
|
-
"@types/node": "^25.
|
|
76
|
+
"@types/node": "^25.9.3",
|
|
72
77
|
"typescript": "^6.0.3"
|
|
73
78
|
},
|
|
74
79
|
"dependencies": {
|
|
75
|
-
"@clack/prompts": "^1.
|
|
80
|
+
"@clack/prompts": "^1.5.1",
|
|
81
|
+
"@types/ms": "^2.1.0",
|
|
76
82
|
"ajv": "^8.17",
|
|
77
|
-
"hono": "^4.12.
|
|
83
|
+
"hono": "^4.12.25",
|
|
84
|
+
"impit": "0.14.1",
|
|
85
|
+
"ioredis": "^5.11.1",
|
|
86
|
+
"ms": "^2.1.3",
|
|
78
87
|
"playwright": "^1.55.1",
|
|
79
|
-
"playwright-
|
|
88
|
+
"playwright-extra": "^4.3.6",
|
|
89
|
+
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
80
90
|
"re2-wasm": "^1.0",
|
|
81
91
|
"safe-regex": "^2.1",
|
|
82
|
-
"tlsclientwrapper": "^4.2.0",
|
|
83
92
|
"zod": "^4.4.3"
|
|
84
93
|
}
|
|
85
94
|
}
|