@apifuse/provider-sdk 2.1.0-beta.0 → 2.1.0-beta.2
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 +35 -26
- package/CHANGELOG.md +14 -0
- package/README.md +104 -2
- package/bin/apifuse-check.ts +30 -2
- package/bin/apifuse-dev.ts +30 -6
- package/bin/apifuse-pack-check.ts +90 -0
- package/bin/apifuse-pack-smoke.ts +296 -0
- package/package.json +9 -8
- package/src/ceremonies/index.ts +22 -1
- package/src/cli/create.ts +2 -3
- package/src/cli/templates/provider/README.md.tpl +57 -2
- package/src/cli/templates/provider/index.ts.tpl +4 -1
- package/src/config/loader.ts +61 -1
- package/src/define.ts +44 -6
- package/src/index.ts +0 -6
- package/src/provider.ts +0 -1
- package/src/runtime/http.ts +27 -11
- package/src/runtime/tls.ts +21 -12
- package/src/server/serve.ts +17 -3
- package/src/types.ts +28 -2
- package/src/composite.ts +0 -43
package/AUTHORING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Canonical scaffolding command: `apifuse create`
|
|
4
4
|
- Monorepo contributors should use `apifuse create <name> --preset monorepo`
|
|
5
|
-
- Standalone
|
|
5
|
+
- Standalone bounty contributors should use `bunx @apifuse/provider-sdk@beta create <name> --yes` until this release is promoted to `latest`
|
|
6
6
|
- Provider server contract is:
|
|
7
7
|
- dev default `3900`
|
|
8
8
|
- start/Docker/container `3000`
|
|
@@ -47,6 +47,7 @@ description:
|
|
|
47
47
|
- `description` — 150+ chars English (error-level rule)
|
|
48
48
|
- Every Zod field in input AND output has `.describe()` including nested objects + array items (error-level rule)
|
|
49
49
|
- `fixtures.request` + `fixtures.response` both present (error-level rule)
|
|
50
|
+
- Exactly one of `healthCheck` or `healthCheckUnsupported` per operation. Prefer `healthCheck` for safe read-only upstream probes; use `healthCheckUnsupported` only with a specific reason for destructive, paid, credential-sensitive, flaky, or otherwise unsafe probes.
|
|
50
51
|
|
|
51
52
|
### Factored operations
|
|
52
53
|
|
|
@@ -62,31 +63,39 @@ Use `defineOperation()` when an operation is large enough to live beside helper
|
|
|
62
63
|
|
|
63
64
|
- `annotations`: `{ readOnly, destructive, idempotent, openWorld, rateLimit }` — agentic safety signals
|
|
64
65
|
- `tags`: operation-level semantic tags for retrieval (e.g., `["weather", "korea", "realtime"]`)
|
|
65
|
-
- `relatedOperations`: `{ alternatives?: string[]
|
|
66
|
-
|
|
67
|
-
###
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
66
|
+
- `relatedOperations`: `{ alternatives?: string[] }` — links to fallback/sibling operations
|
|
67
|
+
|
|
68
|
+
### External bounty submission evidence
|
|
69
|
+
|
|
70
|
+
External contributors are expected to submit standalone Provider source plus:
|
|
71
|
+
|
|
72
|
+
- SDK version/tag and create command used.
|
|
73
|
+
- Provider id, version, runtime, auth mode, and Operation list.
|
|
74
|
+
- Health coverage table for every Operation.
|
|
75
|
+
- `bun run check` output.
|
|
76
|
+
- `bun run test` output.
|
|
77
|
+
- Fixture evidence and known upstream constraints.
|
|
78
|
+
|
|
79
|
+
Maintainers own monorepo import under `providers/<id>/`, registry generation,
|
|
80
|
+
deployment projection checks, and release workflows.
|
|
81
|
+
|
|
82
|
+
### Public local debugging checklist
|
|
83
|
+
|
|
84
|
+
- Operation smoke requests use the provider server envelope:
|
|
85
|
+
`{"requestId":"req_local_<operation>","input":{...},"headers":{}}`.
|
|
86
|
+
Omit `connection` for public/no-auth operations; do not send `connection: null`.
|
|
87
|
+
- Credential-backed smoke requests pass local-only credential material in
|
|
88
|
+
`connection.secrets`. Keep real values in shell env or `.env`, never in source
|
|
89
|
+
or fixtures.
|
|
90
|
+
- Auth-flow debugging starts with `/auth/start`, continues with
|
|
91
|
+
`/auth/continue`, and carries returned `contextPatch` values into the next
|
|
92
|
+
request's `context`.
|
|
93
|
+
- TLS/browser providers may require local runtime setup outside Provider code:
|
|
94
|
+
use `bun pm untrusted`/`bun pm trust koffi` for blocked native TLS dependency
|
|
95
|
+
scripts, `browser.engine: "playwright-stealth"` for TypeScript browser
|
|
96
|
+
Providers (`nodriver` is Python-runtime only), `bunx playwright install
|
|
97
|
+
chromium` for local Playwright browser assets, or
|
|
98
|
+
`CDP_POOL_URL`/`APIFUSE_CDP_POOL_URL` for remote browser debugging.
|
|
90
99
|
|
|
91
100
|
### Running the lint locally
|
|
92
101
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.0-beta.2
|
|
4
|
+
|
|
5
|
+
- Harden public bounty contributor DX with server-contract accurate README and generated Provider smoke examples.
|
|
6
|
+
- Add packed-artifact regression checks so stale `connection: null` or missing `requestId` examples cannot ship again.
|
|
7
|
+
- Extend clean-room packed SDK smoke coverage to boot the generated dev server and call `/health` plus `POST /v1/ping`.
|
|
8
|
+
- Document credential, auth-flow, TLS, browser, and Bun trusted-dependency troubleshooting for SDK-only local development.
|
|
9
|
+
|
|
10
|
+
## 2.1.0-beta.1
|
|
11
|
+
|
|
12
|
+
- Fix public `apifuse create` runtime packaging by publishing `@clack/prompts` as a production dependency.
|
|
13
|
+
- Update generated Provider starter templates so the sample operation declares a local-only `healthCheckUnsupported` and passes the current health coverage contract.
|
|
14
|
+
- Add packed-artifact smoke coverage for the public create/check/test flow before npm release publishing.
|
|
15
|
+
- Document the public SDK-only bounty contributor path and maintainer-owned monorepo import boundary.
|
|
16
|
+
|
|
3
17
|
## 2.1.0-beta.0
|
|
4
18
|
|
|
5
19
|
- BREAKING: collapse the Chrome desktop stealth catalog to `chrome-146` plus the `chrome-desktop` alias. Removed/blocked `chrome-120`, `chrome-124`, `chrome-129`, `chrome-130`, `chrome-131`, `chrome-133`, `chrome-144`, `chrome-146-psk`, `chrome-131-psk`, `chrome-130-psk`, and `edge-131`; migrate callers to `chrome-146`.
|
package/README.md
CHANGED
|
@@ -8,12 +8,19 @@ ApiFuse Provider SDK — build provider declarations and runtimes with one publi
|
|
|
8
8
|
bun add @apifuse/provider-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
For external bounty scaffolding, use the beta tag until this release is promoted
|
|
12
|
+
to `latest`:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bunx @apifuse/provider-sdk@beta create my-provider --yes
|
|
16
|
+
```
|
|
17
|
+
|
|
11
18
|
## Create a provider
|
|
12
19
|
|
|
13
20
|
### Standalone (default)
|
|
14
21
|
|
|
15
22
|
```bash
|
|
16
|
-
bunx @apifuse/provider-sdk create my-provider
|
|
23
|
+
bunx @apifuse/provider-sdk@beta create my-provider --yes
|
|
17
24
|
```
|
|
18
25
|
|
|
19
26
|
The canonical `create` flow:
|
|
@@ -49,11 +56,84 @@ Removed legacy runtime paths are not supported:
|
|
|
49
56
|
|
|
50
57
|
```bash
|
|
51
58
|
cd my-provider
|
|
52
|
-
bun run dev
|
|
53
59
|
bun run check
|
|
54
60
|
bun run test
|
|
61
|
+
bun run dev
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Smoke the generated local server:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
curl -s http://localhost:3900/health
|
|
68
|
+
curl -s -X POST http://localhost:3900/v1/ping \
|
|
69
|
+
-H 'Content-Type: application/json' \
|
|
70
|
+
-d '{"requestId":"req_local_ping","input":{"value":"hello"},"headers":{}}'
|
|
55
71
|
```
|
|
56
72
|
|
|
73
|
+
The operation request body is the same envelope used by the ApiFuse gateway:
|
|
74
|
+
|
|
75
|
+
| Field | Required | Notes |
|
|
76
|
+
|---|---:|---|
|
|
77
|
+
| `requestId` | yes | Any unique string is fine for local debugging; it is echoed in structured errors. |
|
|
78
|
+
| `input` | yes | The operation input after schema validation. |
|
|
79
|
+
| `headers` | no | Extra caller headers to expose through `ctx.request.headers`. |
|
|
80
|
+
| `connection` | no | Omit for no-auth/public operations. For credential debugging, pass an object with `id`, `mode`, `secrets`, `metadata`, and `externalRef`. Do not pass `null`. |
|
|
81
|
+
|
|
82
|
+
Credential-bearing local smoke example:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
curl -s -X POST http://localhost:3900/v1/me \
|
|
86
|
+
-H 'Content-Type: application/json' \
|
|
87
|
+
-d '{
|
|
88
|
+
"requestId":"req_local_me",
|
|
89
|
+
"input":{},
|
|
90
|
+
"connection":{
|
|
91
|
+
"id":"conn_local_debug",
|
|
92
|
+
"mode":"credentials",
|
|
93
|
+
"secrets":{"apiKey":"dev-only-secret"},
|
|
94
|
+
"scopes":[],
|
|
95
|
+
"metadata":{},
|
|
96
|
+
"externalRef":"local-debug"
|
|
97
|
+
}
|
|
98
|
+
}'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Auth-flow endpoints use the same `requestId` convention:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
curl -s -X POST http://localhost:3900/auth/start \
|
|
105
|
+
-H 'Content-Type: application/json' \
|
|
106
|
+
-d '{"requestId":"req_auth_start","flowId":"flow_local_1","providerId":"my-provider","context":{}}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If a local smoke returns `{"error":...}`, inspect the JSON error body and the
|
|
110
|
+
`apifuse dev` server log. Validation failures include a `details` array with
|
|
111
|
+
the bad request path; provider/runtime failures include `code`, `message`, and
|
|
112
|
+
`requestId`.
|
|
113
|
+
|
|
114
|
+
### Local debugging checklist
|
|
115
|
+
|
|
116
|
+
- **`invalid_request` on `/v1/{operation}`**: confirm the request body includes
|
|
117
|
+
`requestId` and `input`. Omit `connection` for public/no-auth operations;
|
|
118
|
+
never send `connection: null`.
|
|
119
|
+
- **Credential-backed operations**: declare `credential.keys`, then pass matching
|
|
120
|
+
local-only values through `connection.secrets`. Read them in handlers with
|
|
121
|
+
`ctx.credential.get("key")` or `ctx.credential.getAccessToken()`.
|
|
122
|
+
- **Provider env secrets**: declare `secrets[]`, set values in your shell or
|
|
123
|
+
`.env`, and read only those names through `ctx.env.get("NAME")`.
|
|
124
|
+
- **Auth flows**: call `/auth/start`, then `/auth/continue` with the same
|
|
125
|
+
`flowId`; preserve any returned `contextPatch` in the next local request's
|
|
126
|
+
`context` object.
|
|
127
|
+
- **TLS-sensitive providers**: if Bun reports blocked lifecycle scripts after
|
|
128
|
+
install, run `bun pm untrusted`; when it lists trusted SDK native dependencies
|
|
129
|
+
such as `koffi`, run `bun pm trust koffi` (or `bun pm trust`) before debugging
|
|
130
|
+
`ctx.tls` failures.
|
|
131
|
+
- **Browser providers**: for TypeScript Providers use `runtime: "browser"` plus
|
|
132
|
+
`browser.engine: "playwright-stealth"`; `nodriver` is a Python-runtime path.
|
|
133
|
+
Install local browser assets with `bunx playwright install chromium` when
|
|
134
|
+
using the Playwright runtime, or set `CDP_POOL_URL`/`APIFUSE_CDP_POOL_URL`
|
|
135
|
+
when debugging against a remote browser pool.
|
|
136
|
+
|
|
57
137
|
## Authoring ergonomics
|
|
58
138
|
|
|
59
139
|
`defineProvider()` infers each operation handler input from the operation `input` schema. For larger providers, factor operations with `defineOperation()` and compose them later:
|
|
@@ -80,6 +160,18 @@ export default defineProvider({
|
|
|
80
160
|
|
|
81
161
|
Operation schemas may be Zod schemas or Standard Schema v1-compatible schemas. Invalid configs throw `ProviderError`/`ValidationError` messages that name the offending field, such as `auth.mode` or `operations.search.fixtures.request`.
|
|
82
162
|
|
|
163
|
+
### Operation health coverage
|
|
164
|
+
|
|
165
|
+
Every operation must declare exactly one of:
|
|
166
|
+
|
|
167
|
+
- `healthCheck` — preferred for safe read-only upstream probes.
|
|
168
|
+
- `healthCheckUnsupported` — allowed only when a probe is destructive, paid,
|
|
169
|
+
credential-sensitive, flaky by design, or otherwise unsafe. The `reason` must
|
|
170
|
+
be specific.
|
|
171
|
+
|
|
172
|
+
The generated `ping` operation uses `healthCheckUnsupported` only because it is
|
|
173
|
+
a local scaffold check, not a real upstream API probe.
|
|
174
|
+
|
|
83
175
|
### Operation annotations
|
|
84
176
|
|
|
85
177
|
Operations declare non-functional metadata via `annotations`:
|
|
@@ -106,6 +198,11 @@ apifuse test [path]
|
|
|
106
198
|
apifuse perf <path> --operation <operation>
|
|
107
199
|
```
|
|
108
200
|
|
|
201
|
+
`apifuse record` is for real upstream-backed operations that declare
|
|
202
|
+
`upstream.baseUrl` and call the upstream through `ctx.http` or `ctx.tls`. The
|
|
203
|
+
generated local-only `ping` operation intentionally has no upstream and should
|
|
204
|
+
be replaced before recording fixtures.
|
|
205
|
+
|
|
109
206
|
## Scope boundary
|
|
110
207
|
|
|
111
208
|
Generator v1 scaffolds **TypeScript providers only** for this redesign. Python generation remains future work.
|
|
@@ -114,3 +211,8 @@ Generator v1 scaffolds **TypeScript providers only** for this redesign. Python g
|
|
|
114
211
|
## Boundary
|
|
115
212
|
|
|
116
213
|
Provider cataloging, deployment enrollment, docs indexing, and runtime discovery are internal platform-registry responsibilities and are not part of the public `@apifuse/provider-sdk` contract.
|
|
214
|
+
|
|
215
|
+
External bounty contributors should submit standalone Provider source plus
|
|
216
|
+
`bun run check` / `bun run test` evidence. ApiFuse maintainers own monorepo
|
|
217
|
+
import, registry generation, deployment projection checks, and release
|
|
218
|
+
publishing.
|
package/bin/apifuse-check.ts
CHANGED
|
@@ -7,13 +7,14 @@ import { pathToFileURL } from "node:url";
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
import type { ProviderDefinition } from "../src";
|
|
10
|
+
import { lintProvider } from "../src/lint";
|
|
10
11
|
import { safeParseSchemaSync } from "../src/schema";
|
|
11
12
|
|
|
12
13
|
const HELP_TEXT = `Usage: apifuse check [path]
|
|
13
14
|
Example: apifuse check providers/airkorea
|
|
14
15
|
Default: apifuse check .`;
|
|
15
16
|
|
|
16
|
-
type CheckResult = {
|
|
17
|
+
export type CheckResult = {
|
|
17
18
|
message: string;
|
|
18
19
|
passed: boolean;
|
|
19
20
|
details?: string[];
|
|
@@ -103,7 +104,7 @@ function resolveFromParents(inputPath: string): string {
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
async function runChecks(providerRoot: string): Promise<CheckResult[]> {
|
|
107
|
+
export async function runChecks(providerRoot: string): Promise<CheckResult[]> {
|
|
107
108
|
const indexPath = resolve(providerRoot, "index.ts");
|
|
108
109
|
const dockerfilePath = resolve(providerRoot, "Dockerfile");
|
|
109
110
|
const packageJsonPath = resolve(providerRoot, "package.json");
|
|
@@ -118,6 +119,7 @@ async function runChecks(providerRoot: string): Promise<CheckResult[]> {
|
|
|
118
119
|
checkOperations(provider),
|
|
119
120
|
checkFixtures(provider),
|
|
120
121
|
checkSchemas(provider),
|
|
122
|
+
checkAuthoringLint(provider),
|
|
121
123
|
checkProviderMetadata(provider),
|
|
122
124
|
checkDockerfile(dockerfilePath),
|
|
123
125
|
checkPackageJson(packageJsonPath),
|
|
@@ -254,6 +256,32 @@ function checkSchemas(provider: ProviderDefinition | undefined): CheckResult {
|
|
|
254
256
|
};
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
function checkAuthoringLint(
|
|
260
|
+
provider: ProviderDefinition | undefined,
|
|
261
|
+
): CheckResult {
|
|
262
|
+
if (!provider) {
|
|
263
|
+
return {
|
|
264
|
+
message: "Provider authoring lint has no error-level diagnostics",
|
|
265
|
+
passed: false,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const diagnostics = lintProvider(provider);
|
|
270
|
+
const errors = diagnostics.filter(
|
|
271
|
+
(diagnostic) => diagnostic.level === "error",
|
|
272
|
+
);
|
|
273
|
+
const details = diagnostics.map((diagnostic) => {
|
|
274
|
+
const field = diagnostic.field ? `${diagnostic.field}: ` : "";
|
|
275
|
+
return `${diagnostic.level.toUpperCase()} ${diagnostic.rule} ${field}${diagnostic.message}`;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
message: "Provider authoring lint has no error-level diagnostics",
|
|
280
|
+
passed: errors.length === 0,
|
|
281
|
+
details,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
257
285
|
function checkProviderMetadata(
|
|
258
286
|
provider: ProviderDefinition | undefined,
|
|
259
287
|
): CheckResult {
|
package/bin/apifuse-dev.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { dirname, relative, resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
import type { ProviderDefinition } from "../src";
|
|
7
7
|
import {
|
|
@@ -51,10 +51,24 @@ export async function main() {
|
|
|
51
51
|
console.log(` POST http://localhost:${port}/auth/poll`);
|
|
52
52
|
console.log(` POST http://localhost:${port}/auth/disconnect`);
|
|
53
53
|
|
|
54
|
+
const firstOperation = Object.keys(provider.operations)[0];
|
|
55
|
+
if (firstOperation) {
|
|
56
|
+
const sampleInput =
|
|
57
|
+
provider.operations[firstOperation]?.fixtures?.request ?? {};
|
|
58
|
+
const sampleBody = JSON.stringify({
|
|
59
|
+
requestId: `req_local_${firstOperation}`,
|
|
60
|
+
input: sampleInput,
|
|
61
|
+
headers: {},
|
|
62
|
+
});
|
|
63
|
+
console.log("\nSmoke:");
|
|
64
|
+
console.log(` curl -s http://localhost:${port}/health`);
|
|
65
|
+
console.log(
|
|
66
|
+
` curl -s -X POST http://localhost:${port}/v1/${firstOperation} -H 'Content-Type: application/json' -d ${shellSingleQuote(sampleBody)}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
console.log("\nHot reload:");
|
|
55
|
-
console.log(
|
|
56
|
-
` bun --hot ${resolveImportPath("apifuse-dev.ts")} ${args[0] ?? "."}`,
|
|
57
|
-
);
|
|
71
|
+
console.log(` ${renderHotReloadCommand(providerPath, port)}`);
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
export function createProviderContext(provider: ProviderDefinition): {
|
|
@@ -111,8 +125,18 @@ function resolveFromParents(inputPath: string): string {
|
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
|
|
114
|
-
function
|
|
115
|
-
|
|
128
|
+
function renderHotReloadCommand(providerPath: string, port: number): string {
|
|
129
|
+
const devEntry = resolve(providerPath, "dev.ts");
|
|
130
|
+
if (existsSync(devEntry)) {
|
|
131
|
+
const relativeDevEntry = relative(process.cwd(), devEntry) || "dev.ts";
|
|
132
|
+
const portPrefix = process.env.PORT ? `PORT=${port} ` : "";
|
|
133
|
+
return `${portPrefix}bun --hot ${relativeDevEntry}`;
|
|
134
|
+
}
|
|
135
|
+
return "rerun `apifuse dev` after edits (no dev.ts entrypoint found)";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function shellSingleQuote(value: string): string {
|
|
139
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
function createUnsupportedBrowserStub(): BrowserClient {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
|
|
6
7
|
const PACK_RESULT_SCHEMA = z.array(
|
|
@@ -28,12 +29,23 @@ if (!first) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const filePaths = (first.files ?? []).map((file) => file.path);
|
|
32
|
+
const requiredPaths = [
|
|
33
|
+
"bin/apifuse.ts",
|
|
34
|
+
"bin/apifuse-create.ts",
|
|
35
|
+
"bin/apifuse-pack-smoke.ts",
|
|
36
|
+
"src/cli/create.ts",
|
|
37
|
+
"src/cli/templates/provider/index.ts.tpl",
|
|
38
|
+
"src/cli/templates/provider/README.md.tpl",
|
|
39
|
+
];
|
|
31
40
|
const forbiddenMatches = filePaths.filter(
|
|
32
41
|
(path) =>
|
|
33
42
|
path.startsWith("src/__tests__/") ||
|
|
34
43
|
path === "src/index.test.ts" ||
|
|
35
44
|
path === "bin/apifuse-init.ts",
|
|
36
45
|
);
|
|
46
|
+
const missingRequiredPaths = requiredPaths.filter(
|
|
47
|
+
(path) => !filePaths.includes(path),
|
|
48
|
+
);
|
|
37
49
|
|
|
38
50
|
if (forbiddenMatches.length > 0) {
|
|
39
51
|
throw new Error(
|
|
@@ -41,7 +53,85 @@ if (forbiddenMatches.length > 0) {
|
|
|
41
53
|
);
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
if (missingRequiredPaths.length > 0) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Packed artifact is missing required public SDK files:\n${missingRequiredPaths.join("\n")}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const packageJsonInput: unknown = JSON.parse(
|
|
63
|
+
readFileSync("package.json", "utf8"),
|
|
64
|
+
);
|
|
65
|
+
const packageJson = z
|
|
66
|
+
.object({
|
|
67
|
+
dependencies: z.record(z.string(), z.string()).optional(),
|
|
68
|
+
devDependencies: z.record(z.string(), z.string()).optional(),
|
|
69
|
+
})
|
|
70
|
+
.parse(packageJsonInput);
|
|
71
|
+
|
|
72
|
+
if (!packageJson.dependencies?.["@clack/prompts"]) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"@clack/prompts is imported by the public create CLI and must be listed in dependencies.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (packageJson.devDependencies?.["@clack/prompts"]) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"@clack/prompts must not be devDependency-only because the published create CLI imports it at runtime.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
assertPublicSmokeDocs("README.md", readFileSync("README.md", "utf8"));
|
|
85
|
+
assertPublicSmokeDocs(
|
|
86
|
+
"src/cli/templates/provider/README.md.tpl",
|
|
87
|
+
readFileSync("src/cli/templates/provider/README.md.tpl", "utf8"),
|
|
88
|
+
);
|
|
89
|
+
|
|
44
90
|
console.log(`Packed artifact OK: ${first.filename}`);
|
|
45
91
|
for (const filePath of filePaths) {
|
|
46
92
|
console.log(` - ${filePath}`);
|
|
47
93
|
}
|
|
94
|
+
|
|
95
|
+
function assertPublicSmokeDocs(label: string, content: string): void {
|
|
96
|
+
if (!content.includes('"requestId":"req_local_ping"')) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`${label} must document the current provider server request envelope with requestId.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (content.includes('"connection":null')) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${label} must not tell public users to send connection:null; omit connection for no-auth operations.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!content.includes("bunx playwright install chromium")) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`${label} must include browser runtime troubleshooting for public SDK-only debugging.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!content.includes("bun pm untrusted")) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`${label} must include Bun trusted-dependency troubleshooting for TLS/browser bounties.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
!content.includes('browser.engine: "playwright-stealth"') ||
|
|
122
|
+
!content.includes("nodriver")
|
|
123
|
+
) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`${label} must clarify that TypeScript browser providers use playwright-stealth and nodriver is not the TypeScript happy path.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
label.includes("templates/provider/README.md.tpl") &&
|
|
131
|
+
!content.includes("bun run record -- --operation <operation>")
|
|
132
|
+
) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`${label} must document fixture recording through a generated package script, not a shell-global apifuse command.`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|