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