@apifuse/provider-sdk 2.0.0-beta.1 → 2.1.0-beta.1

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.
Files changed (79) hide show
  1. package/AUTHORING.md +93 -0
  2. package/CHANGELOG.md +21 -0
  3. package/README.md +133 -28
  4. package/bin/apifuse-check.ts +78 -71
  5. package/bin/apifuse-create.ts +12 -0
  6. package/bin/apifuse-dev.ts +24 -61
  7. package/bin/apifuse-pack-check.ts +87 -0
  8. package/bin/apifuse-pack-smoke.ts +122 -0
  9. package/bin/apifuse-perf.ts +33 -32
  10. package/bin/apifuse-record.ts +17 -7
  11. package/bin/apifuse-test.ts +6 -4
  12. package/bin/apifuse.ts +36 -35
  13. package/package.json +29 -9
  14. package/src/ceremonies/index.ts +768 -0
  15. package/src/cli/commands.ts +87 -0
  16. package/src/cli/create.ts +845 -0
  17. package/src/cli/templates/provider/Dockerfile.tpl +7 -0
  18. package/src/cli/templates/provider/README.md.tpl +41 -0
  19. package/src/cli/templates/provider/dev.ts.tpl +5 -0
  20. package/src/cli/templates/provider/index.test.ts.tpl +13 -0
  21. package/src/cli/templates/provider/index.ts.tpl +58 -0
  22. package/src/cli/templates/provider/start.ts.tpl +5 -0
  23. package/src/config/loader.ts +61 -1
  24. package/src/define.ts +565 -41
  25. package/src/dev.ts +2 -6
  26. package/src/errors.ts +42 -0
  27. package/src/index.ts +44 -38
  28. package/src/lint.ts +574 -0
  29. package/src/provider.ts +13 -0
  30. package/src/runtime/auth-flow.ts +67 -0
  31. package/src/runtime/credential.ts +95 -0
  32. package/src/runtime/env.ts +13 -0
  33. package/src/runtime/executor.ts +13 -14
  34. package/src/runtime/http.ts +36 -12
  35. package/src/runtime/insights.ts +3 -3
  36. package/src/runtime/key-derivation.ts +122 -0
  37. package/src/runtime/keyring.ts +148 -0
  38. package/src/runtime/namespace.ts +33 -0
  39. package/src/runtime/prevalidate.ts +252 -0
  40. package/src/runtime/tls.ts +41 -17
  41. package/src/runtime/waterfall.ts +0 -1
  42. package/src/schema.ts +77 -0
  43. package/src/serve.ts +1 -664
  44. package/src/server/index.ts +22 -0
  45. package/src/server/serve.ts +624 -0
  46. package/src/server/types.ts +78 -0
  47. package/src/stealth/profiles.ts +10 -93
  48. package/src/testing/run.ts +391 -32
  49. package/src/types.ts +390 -41
  50. package/bin/apifuse-init.ts +0 -387
  51. package/src/__tests__/auth.test.ts +0 -396
  52. package/src/__tests__/browser-auth.test.ts +0 -180
  53. package/src/__tests__/browser.test.ts +0 -632
  54. package/src/__tests__/define.test.ts +0 -225
  55. package/src/__tests__/errors.test.ts +0 -69
  56. package/src/__tests__/executor.test.ts +0 -214
  57. package/src/__tests__/http.test.ts +0 -238
  58. package/src/__tests__/insights.test.ts +0 -210
  59. package/src/__tests__/instrumentation.test.ts +0 -290
  60. package/src/__tests__/otlp.test.ts +0 -141
  61. package/src/__tests__/perf.test.ts +0 -60
  62. package/src/__tests__/providers-yaml.test.ts +0 -135
  63. package/src/__tests__/proxy.test.ts +0 -359
  64. package/src/__tests__/recipes.test.ts +0 -36
  65. package/src/__tests__/serve.test.ts +0 -233
  66. package/src/__tests__/session.test.ts +0 -231
  67. package/src/__tests__/state.test.ts +0 -100
  68. package/src/__tests__/stealth.test.ts +0 -57
  69. package/src/__tests__/testing.test.ts +0 -97
  70. package/src/__tests__/tls.test.ts +0 -345
  71. package/src/__tests__/types.test.ts +0 -142
  72. package/src/__tests__/utils.test.ts +0 -62
  73. package/src/__tests__/waterfall.test.ts +0 -270
  74. package/src/config/providers-yaml.ts +0 -370
  75. package/src/index.test.ts +0 -1
  76. package/src/protocol.ts +0 -183
  77. package/src/runtime/auth.ts +0 -245
  78. package/src/runtime/session.ts +0 -573
  79. package/src/runtime/state.ts +0 -124
@@ -1,387 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import { existsSync } from "node:fs";
4
- import { mkdir, writeFile } from "node:fs/promises";
5
- import { dirname, join, resolve } from "node:path";
6
-
7
- import { cancel, intro, isCancel, outro, select, text } from "@clack/prompts";
8
-
9
- const CONNECTOR_NAME_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
10
- const CATEGORIES = [
11
- "developer-tools",
12
- "finance",
13
- "commerce",
14
- "productivity",
15
- "marketing",
16
- "data",
17
- "communication",
18
- "other",
19
- ] as const;
20
-
21
- type AuthMode = "none" | "credentials" | "api-key" | "oauth2";
22
- type RuntimeMode = "standard" | "browser";
23
- type Category = (typeof CATEGORIES)[number];
24
-
25
- type Answers = {
26
- name: string;
27
- displayName: string;
28
- category: Category;
29
- authMode: AuthMode;
30
- runtime: RuntimeMode;
31
- };
32
-
33
- const HELP_TEXT = `Usage: apifuse init <provider-name>
34
- Example: apifuse init my-new-api`;
35
-
36
- export async function main() {
37
- const args = normalizeArgs(process.argv.slice(2));
38
-
39
- if (args.includes("--help") || args.includes("-h")) {
40
- console.log(HELP_TEXT);
41
- return;
42
- }
43
-
44
- intro("Create a new ApiFuse provider");
45
-
46
- const initialName = args[0];
47
- const answers = await promptForAnswers(initialName);
48
- const workspaceRoot = findWorkspaceRoot();
49
- const providerRoot = resolve(workspaceRoot, "providers", answers.name);
50
-
51
- if (existsSync(providerRoot)) {
52
- throw new Error(
53
- `Provider directory already exists: providers/${answers.name}`,
54
- );
55
- }
56
-
57
- await mkdir(join(providerRoot, "__tests__"), { recursive: true });
58
-
59
- await Promise.all([
60
- writeFile(join(providerRoot, "index.ts"), renderIndexTs(answers)),
61
- writeFile(join(providerRoot, "package.json"), renderPackageJson(answers)),
62
- writeFile(join(providerRoot, "Dockerfile"), renderDockerfile()),
63
- writeFile(join(providerRoot, "manifest.json"), renderManifestJson(answers)),
64
- writeFile(join(providerRoot, "dev.ts"), renderDevTs()),
65
- writeFile(join(providerRoot, "tsconfig.json"), renderTsconfig()),
66
- writeFile(
67
- join(providerRoot, "__tests__", "index.test.ts"),
68
- renderTestTs(answers),
69
- ),
70
- ]);
71
-
72
- outro(`Created providers/${answers.name}`);
73
- console.log(`
74
- Created:
75
- providers/${answers.name}/
76
- ├── index.ts
77
- ├── package.json
78
- ├── Dockerfile
79
- ├── manifest.json
80
- ├── dev.ts
81
- ├── tsconfig.json
82
- └── __tests__/
83
- └── index.test.ts
84
- `);
85
- }
86
-
87
- function normalizeArgs(argv: string[]): string[] {
88
- return argv[0] === "init" ? argv.slice(1) : argv;
89
- }
90
-
91
- async function promptForAnswers(initialName?: string): Promise<Answers> {
92
- const name = await promptValue(
93
- text({
94
- message: "Provider name",
95
- initialValue: initialName,
96
- placeholder: "my-new-api",
97
- validate(value) {
98
- const normalized = value?.trim() ?? "";
99
- if (!normalized) {
100
- return "Provider name is required.";
101
- }
102
-
103
- if (!CONNECTOR_NAME_REGEX.test(normalized)) {
104
- return "Use kebab-case, e.g. my-new-api.";
105
- }
106
- },
107
- }),
108
- );
109
-
110
- const displayName = await promptValue(
111
- text({
112
- message: "Display name",
113
- initialValue: toDisplayName(name),
114
- validate(value) {
115
- if (!(value?.trim() ?? "")) {
116
- return "Display name is required.";
117
- }
118
- },
119
- }),
120
- );
121
-
122
- const category = await promptValue<Category>(
123
- select({
124
- message: "Category",
125
- options: CATEGORIES.map((value) => ({ label: value, value })),
126
- }),
127
- );
128
-
129
- const authMode = await promptValue<AuthMode>(
130
- select({
131
- message: "Auth mode",
132
- options: [
133
- { label: "none", value: "none" },
134
- { label: "credentials", value: "credentials" },
135
- { label: "api-key", value: "api-key" },
136
- { label: "oauth2", value: "oauth2" },
137
- ],
138
- }),
139
- );
140
-
141
- const runtime = await promptValue<RuntimeMode>(
142
- select({
143
- message: "Runtime",
144
- options: [
145
- { label: "standard", value: "standard" },
146
- { label: "browser", value: "browser" },
147
- ],
148
- }),
149
- );
150
-
151
- return {
152
- authMode,
153
- category,
154
- displayName: displayName.trim(),
155
- name: name.trim(),
156
- runtime,
157
- };
158
- }
159
-
160
- async function promptValue<T>(prompt: Promise<T | symbol>): Promise<T> {
161
- const result = await prompt;
162
-
163
- if (isCancel(result)) {
164
- cancel("Operation cancelled.");
165
- process.exit(0);
166
- }
167
-
168
- return result;
169
- }
170
-
171
- function findWorkspaceRoot(): string {
172
- let currentDirectory = process.cwd();
173
-
174
- while (true) {
175
- if (existsSync(resolve(currentDirectory, "providers"))) {
176
- return currentDirectory;
177
- }
178
-
179
- const parentDirectory = dirname(currentDirectory);
180
- if (parentDirectory === currentDirectory) {
181
- return process.cwd();
182
- }
183
-
184
- currentDirectory = parentDirectory;
185
- }
186
- }
187
-
188
- function toDisplayName(name: string): string {
189
- return name
190
- .split("-")
191
- .filter(Boolean)
192
- .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
193
- .join(" ");
194
- }
195
-
196
- function renderIndexTs(answers: Answers): string {
197
- const authBlock = renderAuthBlock(answers.authMode);
198
- const browserBlock =
199
- answers.runtime === "browser"
200
- ? '\n browser: {\n engine: "nodriver",\n },'
201
- : "";
202
-
203
- return `import { defineProvider, z } from "@apifuse/provider-sdk";
204
-
205
- const InputSchema = z.object({
206
- value: z.string().default("hello").describe("Sample input value"),
207
- });
208
-
209
- const OutputSchema = z.object({
210
- ok: z.boolean(),
211
- message: z.string(),
212
- });
213
-
214
- export default defineProvider({
215
- id: "${answers.name}",
216
- version: "1.0.0",
217
- runtime: "${answers.runtime}",${browserBlock}
218
- auth: ${authBlock},
219
- meta: {
220
- displayName: "${escapeTemplate(answers.displayName)}",
221
- description: "${escapeTemplate(answers.displayName)} provider",
222
- category: "${answers.category}",
223
- tags: ["${answers.name}", "example"],
224
- },
225
- operations: {
226
- ping: {
227
- description: "Sample operation for local provider development",
228
- input: InputSchema,
229
- output: OutputSchema,
230
- handler: async (_ctx, input) => {
231
- return {
232
- ok: true,
233
- message: "${escapeTemplate(answers.displayName)} received: " + input.value,
234
- };
235
- },
236
- fixtures: {
237
- request: { value: "hello" },
238
- response: { ok: true, message: "${escapeTemplate(answers.displayName)} received: hello" },
239
- },
240
- },
241
- },
242
- });
243
- `;
244
- }
245
-
246
- function renderAuthBlock(authMode: AuthMode): string {
247
- switch (authMode) {
248
- case "none":
249
- return '{ mode: "none" }';
250
- case "credentials":
251
- return `{
252
- mode: "credentials",
253
- fields: [
254
- { name: "username", label: "Username", type: "text", required: true },
255
- { name: "password", label: "Password", type: "password", required: true },
256
- ],
257
- }`;
258
- case "api-key":
259
- return `{
260
- mode: "api-key",
261
- fields: [
262
- { name: "apiKey", label: "API Key", type: "password", required: true },
263
- ],
264
- }`;
265
- case "oauth2":
266
- return `{
267
- mode: "oauth2",
268
- fields: [
269
- { name: "accessToken", label: "Access Token", type: "password", required: true },
270
- ],
271
- }`;
272
- }
273
- }
274
-
275
- function renderPackageJson(answers: Answers): string {
276
- return `${JSON.stringify(
277
- {
278
- name: `@apifuse/provider-${answers.name}`,
279
- version: "1.0.0",
280
- private: true,
281
- type: "module",
282
- main: "./index.ts",
283
- scripts: {
284
- dev: "bun --hot dev.ts",
285
- start: "bun dev.ts",
286
- test: "bun test",
287
- },
288
- dependencies: {
289
- "@apifuse/provider-sdk": "workspace:*",
290
- },
291
- devDependencies: {
292
- "@types/bun": "latest",
293
- },
294
- },
295
- null,
296
- 2,
297
- )}
298
- `;
299
- }
300
-
301
- function renderDockerfile(): string {
302
- return `FROM oven/bun:1.2-alpine
303
- WORKDIR /provider
304
- COPY package.json bun.lockb* ./
305
- RUN bun install --frozen-lockfile || bun install
306
- COPY . .
307
- EXPOSE 3900
308
- CMD ["bun", "run", "start"]
309
- `;
310
- }
311
-
312
- function renderManifestJson(answers: Answers): string {
313
- return `${JSON.stringify(
314
- {
315
- auth: answers.authMode,
316
- category: answers.category,
317
- displayName: answers.displayName,
318
- id: answers.name,
319
- language: "typescript",
320
- runtime: answers.runtime,
321
- sdkVersion: 2,
322
- version: "1.0.0",
323
- },
324
- null,
325
- 2,
326
- )}
327
- `;
328
- }
329
-
330
- function renderDevTs(): string {
331
- return `import { startDevServer } from "@apifuse/provider-sdk";
332
-
333
- import provider from "./index";
334
-
335
- startDevServer(provider, { port: Number(process.env.PORT) || 3900 });
336
- `;
337
- }
338
-
339
- function renderTsconfig(): string {
340
- return `${JSON.stringify(
341
- {
342
- compilerOptions: {
343
- target: "ES2022",
344
- module: "ES2022",
345
- moduleResolution: "bundler",
346
- strict: true,
347
- noEmit: true,
348
- skipLibCheck: true,
349
- resolveJsonModule: true,
350
- },
351
- include: ["**/*.ts"],
352
- exclude: ["node_modules"],
353
- },
354
- null,
355
- 2,
356
- )}
357
- `;
358
- }
359
-
360
- function renderTestTs(answers: Answers): string {
361
- return `import { describe, expect, it } from "bun:test";
362
- import { runStandardTests } from "@apifuse/provider-sdk/testing";
363
-
364
- import provider from "../index";
365
- import manifest from "../manifest.json";
366
-
367
- runStandardTests(provider, undefined, { ...manifest, sdkVersion: 1 });
368
-
369
- describe("${answers.name}", () => {
370
- it("keeps manifest metadata on sdk v2", () => {
371
- expect(manifest.sdkVersion).toBe(2);
372
- expect(manifest.auth).toBe("${answers.authMode}");
373
- });
374
- });
375
- `;
376
- }
377
-
378
- function escapeTemplate(value: string): string {
379
- return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
380
- }
381
-
382
- if (import.meta.main) {
383
- await main().catch((error: unknown) => {
384
- console.error(error instanceof Error ? error.message : String(error));
385
- process.exit(1);
386
- });
387
- }