@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.
- package/AUTHORING.md +218 -21
- package/CHANGELOG.md +54 -0
- package/README.md +147 -10
- package/SUBMISSION.md +87 -0
- package/bin/apifuse-check.ts +86 -4
- package/bin/apifuse-dev.ts +87 -13
- package/bin/apifuse-pack-check.ts +120 -0
- package/bin/apifuse-pack-smoke.ts +423 -0
- package/bin/apifuse-perf.ts +142 -49
- package/bin/apifuse-record.ts +182 -104
- package/bin/apifuse-submit-check.ts +2538 -0
- package/bin/apifuse.ts +1 -1
- package/dist/ceremonies/index.d.ts +41 -0
- package/dist/ceremonies/index.js +490 -0
- package/dist/choice-token.d.ts +24 -0
- package/dist/choice-token.js +74 -0
- package/dist/cli/commands.d.ts +10 -0
- package/dist/cli/commands.js +80 -0
- package/dist/cli/create.d.ts +47 -0
- package/dist/cli/create.js +762 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +160 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +107 -0
- package/dist/config/loader.js +935 -0
- package/dist/contract-json.d.ts +9 -0
- package/dist/contract-json.js +51 -0
- package/dist/contract-serialization.d.ts +4 -0
- package/dist/contract-serialization.js +78 -0
- package/dist/contract-types.d.ts +49 -0
- package/dist/contract-types.js +1 -0
- package/dist/contract.d.ts +6 -0
- package/dist/contract.js +155 -0
- package/dist/define.d.ts +97 -0
- package/dist/define.js +1320 -0
- package/dist/dev.d.ts +9 -0
- package/dist/dev.js +15 -0
- package/dist/errors.d.ts +59 -0
- package/dist/errors.js +97 -0
- package/dist/i18n/catalog.d.ts +29 -0
- package/dist/i18n/catalog.js +159 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/keys.d.ts +10 -0
- package/dist/i18n/keys.js +34 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +37 -0
- package/dist/lint.d.ts +73 -0
- package/dist/lint.js +702 -0
- package/dist/observability.d.ts +5 -0
- package/dist/observability.js +39 -0
- package/dist/provider.d.ts +9 -0
- package/dist/provider.js +8 -0
- package/dist/public-schema-field-lint.d.ts +2 -0
- package/dist/public-schema-field-lint.js +158 -0
- package/dist/recipes/gov-api.d.ts +19 -0
- package/dist/recipes/gov-api.js +72 -0
- package/dist/recipes/rest-api.d.ts +21 -0
- package/dist/recipes/rest-api.js +115 -0
- package/dist/runtime/auth-flow.d.ts +14 -0
- package/dist/runtime/auth-flow.js +44 -0
- package/dist/runtime/browser.d.ts +25 -0
- package/dist/runtime/browser.js +1034 -0
- package/dist/runtime/cache.d.ts +10 -0
- package/dist/runtime/cache.js +372 -0
- package/dist/runtime/choice.d.ts +15 -0
- package/dist/runtime/choice.js +435 -0
- package/dist/runtime/credential.d.ts +8 -0
- package/dist/runtime/credential.js +61 -0
- package/dist/runtime/env.d.ts +2 -0
- package/dist/runtime/env.js +10 -0
- package/dist/runtime/executor.d.ts +16 -0
- package/dist/runtime/executor.js +51 -0
- package/dist/runtime/http.d.ts +8 -0
- package/dist/runtime/http.js +706 -0
- package/dist/runtime/insights.d.ts +9 -0
- package/dist/runtime/insights.js +324 -0
- package/dist/runtime/instrumentation.d.ts +8 -0
- package/dist/runtime/instrumentation.js +269 -0
- package/dist/runtime/key-derivation.d.ts +24 -0
- package/dist/runtime/key-derivation.js +73 -0
- package/dist/runtime/keyring.d.ts +25 -0
- package/dist/runtime/keyring.js +93 -0
- package/dist/runtime/namespace.d.ts +9 -0
- package/dist/runtime/namespace.js +19 -0
- package/dist/runtime/otlp.d.ts +39 -0
- package/dist/runtime/otlp.js +103 -0
- package/dist/runtime/perf.d.ts +12 -0
- package/dist/runtime/perf.js +52 -0
- package/dist/runtime/prevalidate.d.ts +12 -0
- package/dist/runtime/prevalidate.js +173 -0
- package/dist/runtime/provider.d.ts +2 -0
- package/dist/runtime/provider.js +11 -0
- package/dist/runtime/proxy-errors.d.ts +21 -0
- package/dist/runtime/proxy-errors.js +83 -0
- package/dist/runtime/proxy-telemetry.d.ts +8 -0
- package/dist/runtime/proxy-telemetry.js +174 -0
- package/dist/runtime/redis.d.ts +17 -0
- package/dist/runtime/redis.js +82 -0
- package/dist/runtime/request-options.d.ts +3 -0
- package/dist/runtime/request-options.js +42 -0
- package/dist/runtime/state.d.ts +17 -0
- package/dist/runtime/state.js +344 -0
- package/dist/runtime/stealth.d.ts +18 -0
- package/dist/runtime/stealth.js +834 -0
- package/dist/runtime/stt.d.ts +22 -0
- package/dist/runtime/stt.js +480 -0
- package/dist/runtime/trace.d.ts +26 -0
- package/dist/runtime/trace.js +142 -0
- package/dist/runtime/waterfall.d.ts +12 -0
- package/dist/runtime/waterfall.js +147 -0
- package/dist/schema.d.ts +74 -0
- package/dist/schema.js +243 -0
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +2 -0
- package/dist/server/serve.d.ts +64 -0
- package/dist/server/serve.js +1110 -0
- package/dist/server/types.d.ts +136 -0
- package/dist/server/types.js +86 -0
- package/dist/stealth/profiles.d.ts +4 -0
- package/dist/stealth/profiles.js +259 -0
- package/dist/stream.d.ts +44 -0
- package/dist/stream.js +151 -0
- package/dist/testing/helpers.d.ts +23 -0
- package/dist/testing/helpers.js +95 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/run.d.ts +34 -0
- package/dist/testing/run.js +303 -0
- package/dist/types.d.ts +1326 -0
- package/dist/types.js +61 -0
- package/dist/utils/date.d.ts +6 -0
- package/dist/utils/date.js +101 -0
- package/dist/utils/parse.d.ts +16 -0
- package/dist/utils/parse.js +51 -0
- package/dist/utils/text.d.ts +4 -0
- package/dist/utils/text.js +14 -0
- package/dist/utils/transform.d.ts +8 -0
- package/dist/utils/transform.js +48 -0
- package/package.json +57 -29
- package/src/ceremonies/index.ts +30 -3
- package/src/choice-token.ts +165 -0
- package/src/cli/commands.ts +34 -11
- package/src/cli/create.ts +214 -52
- package/src/cli/templates/provider/.dockerignore.tpl +22 -0
- package/src/cli/templates/provider/.gitignore.tpl +22 -0
- package/src/cli/templates/provider/README.md.tpl +134 -2
- 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 -44
- 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 +24 -0
- package/src/cli/templates/provider/schemas/ping.ts.tpl +24 -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 +1282 -7
- package/src/contract-json.ts +75 -0
- package/src/contract-serialization.ts +89 -0
- package/src/contract-types.ts +52 -0
- package/src/contract.ts +215 -0
- package/src/define.ts +1726 -48
- package/src/errors.ts +27 -0
- package/src/i18n/catalog.ts +277 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/keys.ts +64 -0
- package/src/index.ts +174 -15
- package/src/lint.ts +547 -73
- package/src/observability.ts +41 -0
- package/src/provider.ts +104 -5
- package/src/public-schema-field-lint.ts +237 -0
- package/src/runtime/auth-flow.ts +7 -0
- package/src/runtime/browser.ts +762 -51
- package/src/runtime/cache.ts +528 -0
- package/src/runtime/choice.ts +760 -0
- package/src/runtime/executor.ts +32 -3
- package/src/runtime/http.ts +945 -185
- 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/redis.ts +116 -0
- package/src/runtime/request-options.ts +66 -0
- package/src/runtime/state.ts +563 -0
- package/src/runtime/stealth.ts +1159 -0
- package/src/runtime/stt.ts +629 -0
- package/src/runtime/trace.ts +1 -1
- package/src/schema.ts +363 -1
- package/src/server/serve.ts +1172 -76
- package/src/server/types.ts +37 -0
- package/src/stream.ts +210 -0
- package/src/testing/run.ts +31 -5
- package/src/types.ts +1118 -44
- package/src/composite.ts +0 -43
- package/src/runtime/tls.ts +0 -425
- package/src/types/playwright-stealth.d.ts +0 -9
package/src/lint.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { ZodType } from "zod";
|
|
2
2
|
|
|
3
|
+
import { lintPublicSchemaFieldNames } from "./public-schema-field-lint";
|
|
4
|
+
import {
|
|
5
|
+
APIFUSE_DESCRIPTION_KEY_META_KEY,
|
|
6
|
+
APIFUSE_SENSITIVE_META_KEY,
|
|
7
|
+
} from "./schema";
|
|
8
|
+
|
|
3
9
|
type AuthModeLike =
|
|
4
10
|
| "none"
|
|
5
11
|
| "platform-managed"
|
|
@@ -14,13 +20,19 @@ type ProviderAuthLike = {
|
|
|
14
20
|
continue?: unknown;
|
|
15
21
|
poll?: unknown;
|
|
16
22
|
abort?: unknown;
|
|
23
|
+
refresh?: unknown;
|
|
17
24
|
};
|
|
18
25
|
};
|
|
19
26
|
|
|
27
|
+
type ProviderContractMetaLike = {
|
|
28
|
+
publicSchemaFieldNames?: "normalized";
|
|
29
|
+
};
|
|
30
|
+
|
|
20
31
|
type SchemaLike = ZodType & {
|
|
21
32
|
description?: string;
|
|
22
33
|
def?: Record<string, unknown>;
|
|
23
34
|
_def?: Record<string, unknown>;
|
|
35
|
+
meta?: () => Record<string, unknown> | undefined;
|
|
24
36
|
shape?: Record<string, SchemaLike> | (() => Record<string, SchemaLike>);
|
|
25
37
|
element?: SchemaLike;
|
|
26
38
|
items?: SchemaLike[];
|
|
@@ -41,9 +53,21 @@ export interface LintDiagnostic {
|
|
|
41
53
|
field?: string;
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
export type ProviderLintMode = "official" | "standalone";
|
|
57
|
+
|
|
58
|
+
type ProviderLintOptions = {
|
|
59
|
+
mode?: ProviderLintMode;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type ProviderSourceLike = {
|
|
63
|
+
authFlowSource?: string;
|
|
64
|
+
providerSourceFiles?: Record<string, string>;
|
|
65
|
+
operations?: Record<string, { handler?: unknown; source?: string }>;
|
|
66
|
+
};
|
|
67
|
+
|
|
44
68
|
function lintAllowedHosts(
|
|
45
69
|
providerId: string | undefined,
|
|
46
|
-
allowedHosts: string[] | undefined,
|
|
70
|
+
allowedHosts: readonly string[] | undefined,
|
|
47
71
|
): LintDiagnostic[] {
|
|
48
72
|
const prefix = providerId ? `Provider "${providerId}"` : "Provider";
|
|
49
73
|
|
|
@@ -103,7 +127,7 @@ function lintReviewed(
|
|
|
103
127
|
];
|
|
104
128
|
}
|
|
105
129
|
|
|
106
|
-
function hasReusableSecretKeys(keys: string[] | undefined): boolean {
|
|
130
|
+
function hasReusableSecretKeys(keys: readonly string[] | undefined): boolean {
|
|
107
131
|
if (!keys) {
|
|
108
132
|
return false;
|
|
109
133
|
}
|
|
@@ -115,6 +139,18 @@ function hasReusableSecretKeys(keys: string[] | undefined): boolean {
|
|
|
115
139
|
);
|
|
116
140
|
}
|
|
117
141
|
|
|
142
|
+
function hasReusableReloginSecretKeys(
|
|
143
|
+
keys: readonly string[] | undefined,
|
|
144
|
+
): boolean {
|
|
145
|
+
if (!keys) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return keys.some((key) =>
|
|
150
|
+
/(password|passcode|secret|cookie|session)/i.test(key),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
function getAuthFlowSource(provider: {
|
|
119
155
|
auth?: ProviderAuthLike;
|
|
120
156
|
authFlowSource?: string;
|
|
@@ -128,6 +164,7 @@ function getAuthFlowSource(provider: {
|
|
|
128
164
|
provider.auth?.flow?.continue,
|
|
129
165
|
provider.auth?.flow?.poll,
|
|
130
166
|
provider.auth?.flow?.abort,
|
|
167
|
+
provider.auth?.flow?.refresh,
|
|
131
168
|
];
|
|
132
169
|
|
|
133
170
|
return parts
|
|
@@ -143,12 +180,12 @@ function lintAuthModel(provider: {
|
|
|
143
180
|
id?: string;
|
|
144
181
|
auth?: ProviderAuthLike;
|
|
145
182
|
credential?: {
|
|
146
|
-
keys?: string[];
|
|
183
|
+
keys?: readonly string[];
|
|
147
184
|
storesReusableSecret?: boolean;
|
|
148
185
|
justification?: string;
|
|
149
186
|
};
|
|
150
187
|
context?: {
|
|
151
|
-
keys?: string[];
|
|
188
|
+
keys?: readonly string[];
|
|
152
189
|
};
|
|
153
190
|
authFlowSource?: string;
|
|
154
191
|
}): LintDiagnostic[] {
|
|
@@ -200,6 +237,20 @@ function lintAuthModel(provider: {
|
|
|
200
237
|
});
|
|
201
238
|
}
|
|
202
239
|
|
|
240
|
+
if (
|
|
241
|
+
typeof provider.auth?.flow?.refresh === "function" &&
|
|
242
|
+
hasReusableReloginSecretKeys(credentialKeys) &&
|
|
243
|
+
(!provider.credential?.storesReusableSecret ||
|
|
244
|
+
!provider.credential.justification)
|
|
245
|
+
) {
|
|
246
|
+
diagnostics.push({
|
|
247
|
+
rule: "auth-refresh-reusable-secret",
|
|
248
|
+
level: "error",
|
|
249
|
+
field: "credential",
|
|
250
|
+
message: `${providerLabel} must set storesReusableSecret and justification when auth.flow.refresh may silently re-login with reusable credential secrets.`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
203
254
|
if (authMode === "platform-managed" && credentialKeys.length > 0) {
|
|
204
255
|
diagnostics.push({
|
|
205
256
|
rule: "platform-managed-no-credential-keys",
|
|
@@ -359,7 +410,60 @@ function getChildSchemas(
|
|
|
359
410
|
}));
|
|
360
411
|
}
|
|
361
412
|
|
|
362
|
-
function
|
|
413
|
+
function uniqueFields(fields: string[]): string[] {
|
|
414
|
+
return Array.from(new Set(fields));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isSensitiveSchema(schema: unknown): boolean {
|
|
418
|
+
if (!schema || typeof schema !== "object" || !("meta" in schema)) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
const meta = schema.meta;
|
|
422
|
+
if (typeof meta !== "function") return false;
|
|
423
|
+
const metadata = meta.call(schema);
|
|
424
|
+
return (
|
|
425
|
+
!!metadata &&
|
|
426
|
+
typeof metadata === "object" &&
|
|
427
|
+
Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getSchemaMetadata(schema: SchemaLike): Record<string, unknown> {
|
|
432
|
+
return schema.meta?.() ?? {};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function getSchemaDescriptionKey(schema: SchemaLike): string | undefined {
|
|
436
|
+
const value = Reflect.get(
|
|
437
|
+
getSchemaMetadata(schema),
|
|
438
|
+
APIFUSE_DESCRIPTION_KEY_META_KEY,
|
|
439
|
+
);
|
|
440
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const SENSITIVE_FIELD_NAMES = new Set([
|
|
444
|
+
"apikey",
|
|
445
|
+
"authorization",
|
|
446
|
+
"cookie",
|
|
447
|
+
"secret",
|
|
448
|
+
"secrets",
|
|
449
|
+
"token",
|
|
450
|
+
"accesstoken",
|
|
451
|
+
"refreshtoken",
|
|
452
|
+
"password",
|
|
453
|
+
"passwd",
|
|
454
|
+
"otp",
|
|
455
|
+
"otpcode",
|
|
456
|
+
"phone",
|
|
457
|
+
"phonenumber",
|
|
458
|
+
"paymenturl",
|
|
459
|
+
]);
|
|
460
|
+
|
|
461
|
+
function isSensitiveFieldName(name: string): boolean {
|
|
462
|
+
const normalized = name.toLowerCase().replace(/[-_\s]/g, "");
|
|
463
|
+
return SENSITIVE_FIELD_NAMES.has(normalized);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function collectUnmarkedSensitiveFields(
|
|
363
467
|
schema: unknown,
|
|
364
468
|
basePath: string,
|
|
365
469
|
seen = new Set<SchemaLike>(),
|
|
@@ -367,13 +471,80 @@ function collectMissingDescriptions(
|
|
|
367
471
|
if (!isSchema(schema) || seen.has(schema)) {
|
|
368
472
|
return [];
|
|
369
473
|
}
|
|
474
|
+
seen.add(schema);
|
|
475
|
+
const out: string[] = [];
|
|
476
|
+
for (const [key, child] of Object.entries(getObjectShape(schema))) {
|
|
477
|
+
const childPath = basePath ? `${basePath}.${key}` : key;
|
|
478
|
+
if (isSensitiveFieldName(key) && !isSensitiveSchema(child)) {
|
|
479
|
+
out.push(childPath);
|
|
480
|
+
}
|
|
481
|
+
out.push(...collectUnmarkedSensitiveFields(child, childPath, seen));
|
|
482
|
+
}
|
|
483
|
+
for (const child of getChildSchemas(schema)) {
|
|
484
|
+
if (Object.hasOwn(getObjectShape(schema), child.key)) continue;
|
|
485
|
+
const isWrapperNode = [
|
|
486
|
+
"unwrap",
|
|
487
|
+
"innerType",
|
|
488
|
+
"sourceType",
|
|
489
|
+
"schema",
|
|
490
|
+
"type",
|
|
491
|
+
"in",
|
|
492
|
+
"out",
|
|
493
|
+
"option",
|
|
494
|
+
"pipe",
|
|
495
|
+
"payload",
|
|
496
|
+
"item",
|
|
497
|
+
"rest",
|
|
498
|
+
"catchall",
|
|
499
|
+
"keyType",
|
|
500
|
+
"valueType",
|
|
501
|
+
].includes(child.key);
|
|
502
|
+
const childPath =
|
|
503
|
+
child.key === "element" || child.key.startsWith("element.")
|
|
504
|
+
? `${basePath}[]`
|
|
505
|
+
: isWrapperNode || child.key.startsWith("pipe.")
|
|
506
|
+
? basePath
|
|
507
|
+
: basePath
|
|
508
|
+
? `${basePath}.${child.key}`
|
|
509
|
+
: child.key;
|
|
510
|
+
out.push(...collectUnmarkedSensitiveFields(child.schema, childPath, seen));
|
|
511
|
+
}
|
|
512
|
+
return out;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function collectSchemaDescriptionKeyDiagnostics(
|
|
516
|
+
schema: unknown,
|
|
517
|
+
basePath: string,
|
|
518
|
+
seen = new Set<SchemaLike>(),
|
|
519
|
+
requireCurrentDescription = true,
|
|
520
|
+
): LintDiagnostic[] {
|
|
521
|
+
if (!isSchema(schema) || seen.has(schema)) {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
370
524
|
|
|
371
525
|
seen.add(schema);
|
|
372
|
-
const
|
|
526
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
373
527
|
const currentPath = basePath || "schema";
|
|
528
|
+
const hasDescriptionKey = getSchemaDescriptionKey(schema) !== undefined;
|
|
374
529
|
|
|
375
|
-
if (
|
|
376
|
-
|
|
530
|
+
if (schema.description && !hasDescriptionKey) {
|
|
531
|
+
diagnostics.push({
|
|
532
|
+
rule: "schema-description-raw-prose",
|
|
533
|
+
level: "error",
|
|
534
|
+
field: currentPath,
|
|
535
|
+
message: `Schema field "${currentPath}" must use .describeKey() or describeKey() instead of raw static prose.`,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (requireCurrentDescription && !hasDescriptionKey) {
|
|
540
|
+
diagnostics.push({
|
|
541
|
+
rule: "schema-description-key-required",
|
|
542
|
+
level: "error",
|
|
543
|
+
field: currentPath,
|
|
544
|
+
message: schema.description
|
|
545
|
+
? `Schema field "${currentPath}" has a raw description but is missing .describeKey() or describeKey() metadata.`
|
|
546
|
+
: `Schema field "${currentPath}" is missing .describeKey() or describeKey() metadata.`,
|
|
547
|
+
});
|
|
377
548
|
}
|
|
378
549
|
|
|
379
550
|
for (const child of getChildSchemas(schema)) {
|
|
@@ -383,30 +554,42 @@ function collectMissingDescriptions(
|
|
|
383
554
|
"sourceType",
|
|
384
555
|
"schema",
|
|
385
556
|
"type",
|
|
557
|
+
"in",
|
|
558
|
+
"out",
|
|
386
559
|
"option",
|
|
387
560
|
"pipe",
|
|
388
561
|
"payload",
|
|
389
562
|
"item",
|
|
390
563
|
"rest",
|
|
391
564
|
"catchall",
|
|
565
|
+
"keyType",
|
|
566
|
+
"valueType",
|
|
392
567
|
].includes(child.key);
|
|
568
|
+
const isStructuralNode =
|
|
569
|
+
isWrapperNode ||
|
|
570
|
+
child.key.startsWith("pipe.") ||
|
|
571
|
+
child.key === "element" ||
|
|
572
|
+
child.key.startsWith("element.");
|
|
393
573
|
const childPath = isWrapperNode
|
|
394
574
|
? currentPath
|
|
395
575
|
: currentPath === "schema"
|
|
396
576
|
? child.key
|
|
397
577
|
: /^\d+$/.test(child.key)
|
|
398
578
|
? `${currentPath}[${child.key}]`
|
|
399
|
-
: child.key.startsWith("element")
|
|
579
|
+
: child.key === "element" || child.key.startsWith("element.")
|
|
400
580
|
? `${currentPath}[]`
|
|
401
581
|
: `${currentPath}.${child.key}`;
|
|
402
|
-
|
|
582
|
+
diagnostics.push(
|
|
583
|
+
...collectSchemaDescriptionKeyDiagnostics(
|
|
584
|
+
child.schema,
|
|
585
|
+
childPath,
|
|
586
|
+
seen,
|
|
587
|
+
!isStructuralNode,
|
|
588
|
+
),
|
|
589
|
+
);
|
|
403
590
|
}
|
|
404
591
|
|
|
405
|
-
return
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function uniqueFields(fields: string[]): string[] {
|
|
409
|
-
return Array.from(new Set(fields));
|
|
592
|
+
return diagnostics;
|
|
410
593
|
}
|
|
411
594
|
|
|
412
595
|
function isComplexSchema(
|
|
@@ -438,60 +621,296 @@ function hasBidirectionalFixtures(fixtures: unknown): boolean {
|
|
|
438
621
|
return "request" in fixtures && "response" in fixtures;
|
|
439
622
|
}
|
|
440
623
|
|
|
624
|
+
function getOperationSource(operation: {
|
|
625
|
+
handler?: unknown;
|
|
626
|
+
source?: string;
|
|
627
|
+
}): string {
|
|
628
|
+
if (operation.source) {
|
|
629
|
+
return operation.source;
|
|
630
|
+
}
|
|
631
|
+
return typeof operation.handler === "function"
|
|
632
|
+
? operation.handler.toString()
|
|
633
|
+
: "";
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function lintStealthTransportUsage(provider: {
|
|
637
|
+
id?: string;
|
|
638
|
+
stealth?: unknown;
|
|
639
|
+
operations?: Record<string, { handler?: unknown; source?: string }>;
|
|
640
|
+
}): LintDiagnostic[] {
|
|
641
|
+
if (provider.stealth || !provider.operations) {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const providerLabel = provider.id ? `Provider "${provider.id}"` : "Provider";
|
|
646
|
+
return Object.entries(provider.operations).flatMap(
|
|
647
|
+
([operationKey, operation]) => {
|
|
648
|
+
const source = getOperationSource(operation);
|
|
649
|
+
if (!/\bctx\.stealth\b/.test(source)) {
|
|
650
|
+
return [];
|
|
651
|
+
}
|
|
652
|
+
return [
|
|
653
|
+
{
|
|
654
|
+
rule: "stealth-config-required",
|
|
655
|
+
level: "error" as const,
|
|
656
|
+
field: `operations.${operationKey}`,
|
|
657
|
+
message: `${providerLabel} operation "${operationKey}" uses ctx.stealth but provider.stealth is not declared.`,
|
|
658
|
+
},
|
|
659
|
+
];
|
|
660
|
+
},
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function lintCredentialWriteUsage(provider: {
|
|
665
|
+
operations?: Record<string, { handler?: unknown; source?: string }>;
|
|
666
|
+
}): LintDiagnostic[] {
|
|
667
|
+
if (!provider.operations) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return Object.entries(provider.operations).flatMap(
|
|
672
|
+
([operationKey, operation]) => {
|
|
673
|
+
const source = getOperationSource(operation);
|
|
674
|
+
if (!/\bctx\.credential\.(?:set|setMany)\s*\(/.test(source)) {
|
|
675
|
+
return [];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return [
|
|
679
|
+
{
|
|
680
|
+
rule: "ctx-credential-write-forbidden-in-handler",
|
|
681
|
+
level: "error" as const,
|
|
682
|
+
field: `operations.${operationKey}.handler`,
|
|
683
|
+
message:
|
|
684
|
+
"Operation handlers must not mutate credentials; return refreshed credentials from auth.flow.refresh instead.",
|
|
685
|
+
},
|
|
686
|
+
];
|
|
687
|
+
},
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function lintPlaywrightDirectImports(provider: {
|
|
692
|
+
authFlowSource?: string;
|
|
693
|
+
providerSourceFiles?: Record<string, string>;
|
|
694
|
+
operations?: Record<string, { handler?: unknown; source?: string }>;
|
|
695
|
+
}): LintDiagnostic[] {
|
|
696
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
697
|
+
const importPattern =
|
|
698
|
+
/(?:import\s+(?:type\s+)?[\s\S]*?\s+from\s+["'](?:playwright|playwright-core)["']|require\(\s*["'](?:playwright|playwright-core)["']\s*\)|import\(\s*["'](?:playwright|playwright-core)["']\s*\))/;
|
|
699
|
+
|
|
700
|
+
if (provider.authFlowSource && importPattern.test(provider.authFlowSource)) {
|
|
701
|
+
diagnostics.push({
|
|
702
|
+
rule: "playwright-direct-import",
|
|
703
|
+
level: "warn",
|
|
704
|
+
field: "auth.flow",
|
|
705
|
+
message:
|
|
706
|
+
"Provider auth flow imports playwright directly; use ctx.browser frame-aware methods so the SDK can enforce the CDP pool runtime.",
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
for (const [filePath, source] of Object.entries(
|
|
711
|
+
provider.providerSourceFiles ?? {},
|
|
712
|
+
)) {
|
|
713
|
+
if (!importPattern.test(source)) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
diagnostics.push({
|
|
718
|
+
rule: "playwright-direct-import",
|
|
719
|
+
level: "warn",
|
|
720
|
+
field: `sourceFiles.${filePath}`,
|
|
721
|
+
message:
|
|
722
|
+
"Provider source imports playwright directly; use ctx.browser frame-aware methods so the SDK can enforce the CDP pool runtime.",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (!provider.operations) {
|
|
727
|
+
return diagnostics;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
for (const [operationKey, operation] of Object.entries(provider.operations)) {
|
|
731
|
+
const source = getOperationSource(operation);
|
|
732
|
+
if (!importPattern.test(source)) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
diagnostics.push({
|
|
737
|
+
rule: "playwright-direct-import",
|
|
738
|
+
level: "warn",
|
|
739
|
+
field: `operations.${operationKey}.handler`,
|
|
740
|
+
message:
|
|
741
|
+
"Operation source imports playwright directly; use ctx.browser frame-aware methods so the SDK can enforce the CDP pool runtime.",
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return diagnostics;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
type SelfHostedBrowserPattern = {
|
|
749
|
+
rule: string;
|
|
750
|
+
pattern: RegExp;
|
|
751
|
+
message: string;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const SELF_HOSTED_BROWSER_MESSAGE =
|
|
755
|
+
"Official browser providers must use ctx.browser backed by the managed CDP Pool; do not launch or connect to provider-local Chrome/CDP runtimes.";
|
|
756
|
+
|
|
757
|
+
const SELF_HOSTED_BROWSER_PATTERNS: readonly SelfHostedBrowserPattern[] = [
|
|
758
|
+
{
|
|
759
|
+
rule: "browser-self-hosted-launch",
|
|
760
|
+
pattern: /\b(?:playwright|chromium|firefox|webkit|puppeteer)\.launch\s*\(/,
|
|
761
|
+
message: `${SELF_HOSTED_BROWSER_MESSAGE} Replace direct Playwright/Puppeteer launch calls with ctx.browser.newPage() or ctx.browser.withIsolatedContext().`,
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
rule: "browser-self-hosted-child-process",
|
|
765
|
+
pattern:
|
|
766
|
+
/(?:\b(?:spawn|spawnSync|exec|execSync|execFile|execFileSync)\s*\([\s\S]{0,240}\b(?:google-chrome|chrome|chromium|chromium-browser)\b|\b(?:Bun\.)?spawn(?:Sync)?\s*\([\s\S]{0,240}\b(?:google-chrome|chrome|chromium|chromium-browser)\b|\$`[\s\S]{0,240}\b(?:google-chrome|chrome|chromium|chromium-browser)\b)/,
|
|
767
|
+
message: `${SELF_HOSTED_BROWSER_MESSAGE} Provider pods must not start Chrome with child_process, Bun.spawn, or shell commands.`,
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
rule: "browser-self-hosted-remote-debugging-port",
|
|
771
|
+
pattern:
|
|
772
|
+
/(?:\b(?:google-chrome|chrome|chromium|chromium-browser)\b[\s\S]{0,240}--remote-debugging-port\b|--remote-debugging-port(?:=|\s+))/,
|
|
773
|
+
message: `${SELF_HOSTED_BROWSER_MESSAGE} Provider entrypoints, Dockerfiles, and scripts must not start Chrome with a remote debugging port; use the managed CDP Pool instead.`,
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
rule: "browser-direct-cdp-version-poll",
|
|
777
|
+
pattern: /\/json\/version\b/,
|
|
778
|
+
message: `${SELF_HOSTED_BROWSER_MESSAGE} Do not poll /json/version from provider code; the SDK manages CDP leases through APIFUSE__CDP_POOL__URL.`,
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
rule: "browser-provider-local-cdp-env",
|
|
782
|
+
pattern:
|
|
783
|
+
/\b(?!APIFUSE__CDP_POOL__URL\b)[A-Z][A-Z0-9_]*_CDP_URL\b|process\.env(?:\.(?!APIFUSE__CDP_POOL__URL\b)[A-Z0-9_]*_CDP_URL\b|\[\s*["'`](?!APIFUSE__CDP_POOL__URL\b)[A-Z0-9_]*_CDP_URL["'`]\s*\])/,
|
|
784
|
+
message: `${SELF_HOSTED_BROWSER_MESSAGE} Do not read provider-local CDP endpoint env vars including AMAZON_CDP_URL or custom *_CDP_URL names; production uses APIFUSE__CDP_POOL__URL through ctx.browser.`,
|
|
785
|
+
},
|
|
786
|
+
];
|
|
787
|
+
|
|
788
|
+
function lintSelfHostedBrowserPatterns(
|
|
789
|
+
provider: ProviderSourceLike,
|
|
790
|
+
options: ProviderLintOptions,
|
|
791
|
+
): LintDiagnostic[] {
|
|
792
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
793
|
+
const level = options.mode === "standalone" ? "warn" : "error";
|
|
794
|
+
const sources: Array<{ field: string; source: string }> = [];
|
|
795
|
+
|
|
796
|
+
if (provider.authFlowSource) {
|
|
797
|
+
sources.push({ field: "auth.flow", source: provider.authFlowSource });
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
for (const [filePath, source] of Object.entries(
|
|
801
|
+
provider.providerSourceFiles ?? {},
|
|
802
|
+
)) {
|
|
803
|
+
sources.push({ field: `sourceFiles.${filePath}`, source });
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
for (const [operationKey, operation] of Object.entries(
|
|
807
|
+
provider.operations ?? {},
|
|
808
|
+
)) {
|
|
809
|
+
const source = getOperationSource(operation);
|
|
810
|
+
if (source) {
|
|
811
|
+
sources.push({
|
|
812
|
+
field: `operations.${operationKey}.handler`,
|
|
813
|
+
source,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
for (const { field, source } of sources) {
|
|
819
|
+
for (const item of SELF_HOSTED_BROWSER_PATTERNS) {
|
|
820
|
+
item.pattern.lastIndex = 0;
|
|
821
|
+
if (!item.pattern.test(source)) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
diagnostics.push({
|
|
825
|
+
rule: item.rule,
|
|
826
|
+
level,
|
|
827
|
+
field,
|
|
828
|
+
message: item.message,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return diagnostics;
|
|
834
|
+
}
|
|
835
|
+
|
|
441
836
|
export function lintOperation(op: {
|
|
442
|
-
description
|
|
837
|
+
description?: string;
|
|
838
|
+
descriptionKey?: string;
|
|
839
|
+
whenToUse?: readonly string[];
|
|
840
|
+
whenToUseKeys?: readonly string[];
|
|
841
|
+
whenNotToUse?: readonly string[];
|
|
842
|
+
whenNotToUseKeys?: readonly string[];
|
|
443
843
|
input: unknown;
|
|
444
844
|
output: unknown;
|
|
445
845
|
fixtures?: unknown;
|
|
446
|
-
inputExamples?: unknown[];
|
|
846
|
+
inputExamples?: readonly unknown[];
|
|
447
847
|
derivations?: Record<string, string>;
|
|
448
848
|
}): LintDiagnostic[] {
|
|
449
849
|
const diagnostics: LintDiagnostic[] = [];
|
|
450
850
|
const description = op.description ?? "";
|
|
851
|
+
const hasDescriptionKey =
|
|
852
|
+
typeof op.descriptionKey === "string" && op.descriptionKey.length > 0;
|
|
451
853
|
|
|
452
|
-
if (description.length
|
|
854
|
+
if (description.trim().length > 0 && !hasDescriptionKey) {
|
|
453
855
|
diagnostics.push({
|
|
454
|
-
rule: "description-
|
|
856
|
+
rule: "operation-description-raw-prose",
|
|
455
857
|
level: "error",
|
|
456
858
|
field: "description",
|
|
457
|
-
message:
|
|
859
|
+
message:
|
|
860
|
+
"Operation description must use descriptionKey instead of raw static prose.",
|
|
458
861
|
});
|
|
459
862
|
}
|
|
460
863
|
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
!(lowerDescription.includes("use") && lowerDescription.includes("when"))
|
|
464
|
-
) {
|
|
864
|
+
if (!hasDescriptionKey && description.length < 150) {
|
|
465
865
|
diagnostics.push({
|
|
466
|
-
rule: "description-
|
|
467
|
-
level: "
|
|
866
|
+
rule: "description-min-length",
|
|
867
|
+
level: "error",
|
|
468
868
|
field: "description",
|
|
469
|
-
message:
|
|
869
|
+
message: "Operation description must be at least 150 characters.",
|
|
470
870
|
});
|
|
471
871
|
}
|
|
472
872
|
|
|
473
|
-
|
|
474
|
-
collectMissingDescriptions(op.input, "input"),
|
|
475
|
-
)) {
|
|
873
|
+
if ((op.whenToUse?.length ?? 0) > 0 && !(op.whenToUseKeys?.length ?? 0)) {
|
|
476
874
|
diagnostics.push({
|
|
477
|
-
rule: "
|
|
875
|
+
rule: "operation-when-to-use-raw-prose",
|
|
478
876
|
level: "error",
|
|
479
|
-
field,
|
|
480
|
-
message:
|
|
877
|
+
field: "whenToUse",
|
|
878
|
+
message:
|
|
879
|
+
"Operation whenToUse must use whenToUseKeys instead of raw static prose.",
|
|
481
880
|
});
|
|
482
881
|
}
|
|
483
882
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
883
|
+
if (
|
|
884
|
+
(op.whenNotToUse?.length ?? 0) > 0 &&
|
|
885
|
+
!(op.whenNotToUseKeys?.length ?? 0)
|
|
886
|
+
) {
|
|
487
887
|
diagnostics.push({
|
|
488
|
-
rule: "
|
|
888
|
+
rule: "operation-when-not-to-use-raw-prose",
|
|
489
889
|
level: "error",
|
|
490
|
-
field,
|
|
491
|
-
message:
|
|
890
|
+
field: "whenNotToUse",
|
|
891
|
+
message:
|
|
892
|
+
"Operation whenNotToUse must use whenNotToUseKeys instead of raw static prose.",
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const lowerDescription = description.toLowerCase();
|
|
897
|
+
if (
|
|
898
|
+
!hasDescriptionKey &&
|
|
899
|
+
!(lowerDescription.includes("use") && lowerDescription.includes("when"))
|
|
900
|
+
) {
|
|
901
|
+
diagnostics.push({
|
|
902
|
+
rule: "description-has-when-clause",
|
|
903
|
+
level: "warn",
|
|
904
|
+
field: "description",
|
|
905
|
+
message: 'Operation description should include both "use" and "when".',
|
|
492
906
|
});
|
|
493
907
|
}
|
|
494
908
|
|
|
909
|
+
diagnostics.push(
|
|
910
|
+
...collectSchemaDescriptionKeyDiagnostics(op.input, "input"),
|
|
911
|
+
...collectSchemaDescriptionKeyDiagnostics(op.output, "output"),
|
|
912
|
+
);
|
|
913
|
+
|
|
495
914
|
if (!hasBidirectionalFixtures(op.fixtures)) {
|
|
496
915
|
diagnostics.push({
|
|
497
916
|
rule: "fixtures-both-directions",
|
|
@@ -511,39 +930,80 @@ export function lintOperation(op: {
|
|
|
511
930
|
});
|
|
512
931
|
}
|
|
513
932
|
|
|
933
|
+
for (const field of uniqueFields(
|
|
934
|
+
collectUnmarkedSensitiveFields(op.input, "input"),
|
|
935
|
+
)) {
|
|
936
|
+
diagnostics.push({
|
|
937
|
+
rule: "sensitive-field-unmarked",
|
|
938
|
+
level: "warn",
|
|
939
|
+
field,
|
|
940
|
+
message: `Schema field "${field}" looks sensitive; mark it with fields.*(), field(..., { sensitive: true }), or sensitive(...).`,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
for (const field of uniqueFields(
|
|
945
|
+
collectUnmarkedSensitiveFields(op.output, "output"),
|
|
946
|
+
)) {
|
|
947
|
+
diagnostics.push({
|
|
948
|
+
rule: "sensitive-field-unmarked",
|
|
949
|
+
level: "warn",
|
|
950
|
+
field,
|
|
951
|
+
message: `Schema field "${field}" looks sensitive; mark it with fields.*(), field(..., { sensitive: true }), or sensitive(...).`,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
514
955
|
return diagnostics;
|
|
515
956
|
}
|
|
516
957
|
|
|
517
|
-
export function lintProvider(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
string
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
958
|
+
export function lintProvider(
|
|
959
|
+
provider: {
|
|
960
|
+
id?: string;
|
|
961
|
+
allowedHosts?: readonly string[];
|
|
962
|
+
stealth?: unknown;
|
|
963
|
+
auth?: ProviderAuthLike;
|
|
964
|
+
credential?: {
|
|
965
|
+
keys?: readonly string[];
|
|
966
|
+
storesReusableSecret?: boolean;
|
|
967
|
+
justification?: string;
|
|
968
|
+
};
|
|
969
|
+
context?: {
|
|
970
|
+
keys?: readonly string[];
|
|
971
|
+
};
|
|
972
|
+
authFlowSource?: string;
|
|
973
|
+
providerSourceFiles?: Record<string, string>;
|
|
974
|
+
operations?: Record<
|
|
975
|
+
string,
|
|
976
|
+
{
|
|
977
|
+
description?: string;
|
|
978
|
+
descriptionKey?: string;
|
|
979
|
+
whenToUse?: readonly string[];
|
|
980
|
+
whenToUseKeys?: readonly string[];
|
|
981
|
+
whenNotToUse?: readonly string[];
|
|
982
|
+
whenNotToUseKeys?: readonly string[];
|
|
983
|
+
input: unknown;
|
|
984
|
+
output: unknown;
|
|
985
|
+
fixtures?: unknown;
|
|
986
|
+
inputExamples?: readonly unknown[];
|
|
987
|
+
derivations?: Record<string, string>;
|
|
988
|
+
handler?: unknown;
|
|
989
|
+
source?: string;
|
|
990
|
+
}
|
|
991
|
+
>;
|
|
992
|
+
meta?: {
|
|
993
|
+
contract?: ProviderContractMetaLike;
|
|
994
|
+
};
|
|
995
|
+
reviewed?: string;
|
|
996
|
+
},
|
|
997
|
+
options: ProviderLintOptions = {},
|
|
998
|
+
): LintDiagnostic[] {
|
|
543
999
|
const diagnostics: LintDiagnostic[] = [
|
|
544
1000
|
...lintAllowedHosts(provider.id, provider.allowedHosts),
|
|
545
1001
|
...lintReviewed(provider.id, provider.reviewed),
|
|
546
1002
|
...lintAuthModel(provider),
|
|
1003
|
+
...lintStealthTransportUsage(provider),
|
|
1004
|
+
...lintCredentialWriteUsage(provider),
|
|
1005
|
+
...lintPlaywrightDirectImports(provider),
|
|
1006
|
+
...lintSelfHostedBrowserPatterns(provider, options),
|
|
547
1007
|
];
|
|
548
1008
|
|
|
549
1009
|
if (!provider.operations) {
|
|
@@ -553,14 +1013,28 @@ export function lintProvider(provider: {
|
|
|
553
1013
|
diagnostics.push(
|
|
554
1014
|
...Object.entries(provider.operations).flatMap(
|
|
555
1015
|
([operationKey, operation]) =>
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1016
|
+
[
|
|
1017
|
+
...lintOperation({
|
|
1018
|
+
description: operation.description ?? "",
|
|
1019
|
+
descriptionKey: operation.descriptionKey,
|
|
1020
|
+
whenToUse: operation.whenToUse,
|
|
1021
|
+
whenToUseKeys: operation.whenToUseKeys,
|
|
1022
|
+
whenNotToUse: operation.whenNotToUse,
|
|
1023
|
+
whenNotToUseKeys: operation.whenNotToUseKeys,
|
|
1024
|
+
input: operation.input,
|
|
1025
|
+
output: operation.output,
|
|
1026
|
+
fixtures: operation.fixtures,
|
|
1027
|
+
inputExamples: operation.inputExamples,
|
|
1028
|
+
derivations: operation.derivations,
|
|
1029
|
+
}),
|
|
1030
|
+
...lintPublicSchemaFieldNames(
|
|
1031
|
+
provider.id,
|
|
1032
|
+
operationKey,
|
|
1033
|
+
operation.input,
|
|
1034
|
+
operation.output,
|
|
1035
|
+
provider.meta?.contract?.publicSchemaFieldNames === "normalized",
|
|
1036
|
+
),
|
|
1037
|
+
].map((diagnostic) => ({
|
|
564
1038
|
...diagnostic,
|
|
565
1039
|
field: diagnostic.field
|
|
566
1040
|
? `operations.${operationKey}.${diagnostic.field}`
|