@apifuse/provider-sdk 2.1.0-beta.2 → 2.1.0-beta.20
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 +85 -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 +2849 -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 +745 -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 +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/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 +56 -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 +222 -128
- 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 +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/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/src/cli/create.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
cancel,
|
|
9
|
-
intro,
|
|
10
|
-
isCancel,
|
|
11
|
-
note,
|
|
12
|
-
outro,
|
|
13
|
-
select,
|
|
14
|
-
text,
|
|
15
|
-
} from "@clack/prompts";
|
|
7
|
+
import { cancel, intro, isCancel, note, outro, select, text } from "@clack/prompts";
|
|
16
8
|
import { z } from "zod";
|
|
17
9
|
|
|
18
10
|
import packageJson from "../../package.json";
|
|
@@ -28,12 +20,7 @@ export const CATEGORY_OPTIONS = [
|
|
|
28
20
|
"communication",
|
|
29
21
|
"other",
|
|
30
22
|
] as const;
|
|
31
|
-
export const AUTH_MODE_OPTIONS = [
|
|
32
|
-
"none",
|
|
33
|
-
"platform-managed",
|
|
34
|
-
"credentials",
|
|
35
|
-
"oauth2",
|
|
36
|
-
] as const;
|
|
23
|
+
export const AUTH_MODE_OPTIONS = ["none", "platform-managed", "credentials", "oauth2"] as const;
|
|
37
24
|
export const RUNTIME_OPTIONS = ["standard", "browser"] as const;
|
|
38
25
|
export const PRESET_OPTIONS = ["standalone", "monorepo"] as const;
|
|
39
26
|
|
|
@@ -95,17 +82,13 @@ export type ProviderCreatePlan = {
|
|
|
95
82
|
workspaceRoot?: string;
|
|
96
83
|
};
|
|
97
84
|
|
|
98
|
-
const TEMPLATE_DIR = fileURLToPath(
|
|
99
|
-
new URL("./templates/provider/", import.meta.url),
|
|
100
|
-
);
|
|
85
|
+
const TEMPLATE_DIR = fileURLToPath(new URL("./templates/provider/", import.meta.url));
|
|
101
86
|
const HELP_TEXT = `Usage: apifuse create <provider-name> [options]
|
|
102
87
|
Examples:
|
|
103
88
|
apifuse create my-provider
|
|
104
|
-
apifuse create my-provider --preset monorepo
|
|
105
89
|
apifuse create --config ./apifuse.create.json --json
|
|
106
90
|
|
|
107
91
|
Options:
|
|
108
|
-
--preset <standalone|monorepo>
|
|
109
92
|
--config <path>
|
|
110
93
|
--output-dir <path>
|
|
111
94
|
--display-name <name>
|
|
@@ -115,7 +98,7 @@ Options:
|
|
|
115
98
|
--yes
|
|
116
99
|
--dry-run
|
|
117
100
|
--json
|
|
118
|
-
--sdk-specifier <specifier> # internal/testing override for
|
|
101
|
+
--sdk-specifier <specifier> # internal/testing override for dependency resolution
|
|
119
102
|
--help, -h`;
|
|
120
103
|
|
|
121
104
|
export async function main() {
|
|
@@ -128,9 +111,7 @@ export async function main() {
|
|
|
128
111
|
}
|
|
129
112
|
|
|
130
113
|
const parsed = parseArgs(normalizedArgs);
|
|
131
|
-
const config = parsed.configPath
|
|
132
|
-
? await loadConfig(parsed.configPath)
|
|
133
|
-
: undefined;
|
|
114
|
+
const config = parsed.configPath ? await loadConfig(parsed.configPath) : undefined;
|
|
134
115
|
const resolved = await resolveCreateOptions(parsed, config, process.cwd());
|
|
135
116
|
const plan = await buildProviderCreatePlan(resolved, process.cwd());
|
|
136
117
|
|
|
@@ -197,18 +178,10 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
197
178
|
parsed.displayName = ensureValue(flag, consumeValue());
|
|
198
179
|
break;
|
|
199
180
|
case "--category":
|
|
200
|
-
parsed.category = parseEnum(
|
|
201
|
-
"category",
|
|
202
|
-
consumeValue(),
|
|
203
|
-
CATEGORY_OPTIONS,
|
|
204
|
-
);
|
|
181
|
+
parsed.category = parseEnum("category", consumeValue(), CATEGORY_OPTIONS);
|
|
205
182
|
break;
|
|
206
183
|
case "--auth-mode":
|
|
207
|
-
parsed.authMode = parseEnum(
|
|
208
|
-
"auth mode",
|
|
209
|
-
consumeValue(),
|
|
210
|
-
AUTH_MODE_OPTIONS,
|
|
211
|
-
);
|
|
184
|
+
parsed.authMode = parseEnum("auth mode", consumeValue(), AUTH_MODE_OPTIONS);
|
|
212
185
|
break;
|
|
213
186
|
case "--runtime":
|
|
214
187
|
parsed.runtime = parseEnum("runtime", consumeValue(), RUNTIME_OPTIONS);
|
|
@@ -255,9 +228,7 @@ function parseEnum<T extends readonly string[]>(
|
|
|
255
228
|
const resolvedValue = ensureValue(`--${label}`, value);
|
|
256
229
|
const matchedValue = options.find((option) => option === resolvedValue);
|
|
257
230
|
if (!matchedValue) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`,
|
|
260
|
-
);
|
|
231
|
+
throw new Error(`Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`);
|
|
261
232
|
}
|
|
262
233
|
return matchedValue;
|
|
263
234
|
}
|
|
@@ -273,39 +244,32 @@ async function resolveCreateOptions(
|
|
|
273
244
|
config: CreateConfigFile | undefined,
|
|
274
245
|
cwd: string,
|
|
275
246
|
): Promise<CreateResolvedOptions> {
|
|
276
|
-
const
|
|
277
|
-
const detectedPreset: CreatePreset = detectedWorkspaceRoot
|
|
278
|
-
? "monorepo"
|
|
279
|
-
: "standalone";
|
|
247
|
+
const internalWorkspaceRoot = findApifuseInternalWorkspaceRoot(cwd);
|
|
280
248
|
|
|
281
249
|
const partial: Partial<CreateResolvedOptions> = {
|
|
282
250
|
name: parsed.name ?? config?.name,
|
|
283
|
-
preset: parsed.preset ?? config?.preset ??
|
|
251
|
+
preset: parsed.preset ?? config?.preset ?? "standalone",
|
|
284
252
|
outputDir: parsed.outputDir ?? config?.outputDir,
|
|
285
253
|
displayName: parsed.displayName ?? config?.displayName,
|
|
286
254
|
category: parsed.category ?? config?.category,
|
|
287
255
|
authMode: parsed.authMode ?? config?.authMode,
|
|
288
256
|
runtime: parsed.runtime ?? config?.runtime,
|
|
289
257
|
sdkSpecifier:
|
|
290
|
-
parsed.sdkSpecifier ??
|
|
291
|
-
config?.sdkSpecifier ??
|
|
292
|
-
process.env.APIFUSE_SDK_SPECIFIER,
|
|
258
|
+
parsed.sdkSpecifier ?? config?.sdkSpecifier ?? process.env.APIFUSE__SDK__SPECIFIER,
|
|
293
259
|
dryRun: parsed.dryRun,
|
|
294
260
|
json: parsed.json,
|
|
295
261
|
yes: parsed.yes,
|
|
296
262
|
};
|
|
297
263
|
|
|
298
|
-
if (partial.preset === "monorepo" && !
|
|
264
|
+
if (partial.preset === "monorepo" && !internalWorkspaceRoot) {
|
|
299
265
|
throw new Error(
|
|
300
|
-
"Monorepo preset
|
|
266
|
+
"Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
|
|
301
267
|
);
|
|
302
268
|
}
|
|
303
269
|
|
|
304
270
|
if (partial.yes) {
|
|
305
271
|
if (!partial.name) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
"--yes requires a provider name (positional or via config).",
|
|
308
|
-
);
|
|
272
|
+
throw new Error("--yes requires a provider name (positional or via config).");
|
|
309
273
|
}
|
|
310
274
|
|
|
311
275
|
return {
|
|
@@ -314,7 +278,7 @@ async function resolveCreateOptions(
|
|
|
314
278
|
category: partial.category ?? "other",
|
|
315
279
|
authMode: partial.authMode ?? "none",
|
|
316
280
|
runtime: partial.runtime ?? "standard",
|
|
317
|
-
preset: partial.preset ??
|
|
281
|
+
preset: partial.preset ?? "standalone",
|
|
318
282
|
outputDir: partial.outputDir,
|
|
319
283
|
dryRun: partial.dryRun ?? false,
|
|
320
284
|
json: partial.json ?? false,
|
|
@@ -324,10 +288,10 @@ async function resolveCreateOptions(
|
|
|
324
288
|
}
|
|
325
289
|
|
|
326
290
|
if (!partial.json) {
|
|
327
|
-
intro("Create a new
|
|
291
|
+
intro("Create a new APIFuse provider");
|
|
328
292
|
note(
|
|
329
|
-
|
|
330
|
-
"
|
|
293
|
+
"External bounty workspaces are one-provider repositories. The public create flow defaults to standalone.",
|
|
294
|
+
"Provider workspace",
|
|
331
295
|
);
|
|
332
296
|
}
|
|
333
297
|
|
|
@@ -392,22 +356,7 @@ async function resolveCreateOptions(
|
|
|
392
356
|
initialValue: "standard",
|
|
393
357
|
}),
|
|
394
358
|
)),
|
|
395
|
-
preset:
|
|
396
|
-
partial.preset ??
|
|
397
|
-
(await promptValue(
|
|
398
|
-
select({
|
|
399
|
-
message: "Generation preset",
|
|
400
|
-
options: PRESET_OPTIONS.map((value) => ({
|
|
401
|
-
label: value,
|
|
402
|
-
value,
|
|
403
|
-
hint:
|
|
404
|
-
value === "standalone"
|
|
405
|
-
? "Create a clean-room npm-ready provider package."
|
|
406
|
-
: "Create the provider inside the current ApiFuse monorepo.",
|
|
407
|
-
})),
|
|
408
|
-
initialValue: detectedPreset,
|
|
409
|
-
}),
|
|
410
|
-
)),
|
|
359
|
+
preset: partial.preset ?? "standalone",
|
|
411
360
|
outputDir: partial.outputDir,
|
|
412
361
|
dryRun: partial.dryRun ?? false,
|
|
413
362
|
json: partial.json ?? false,
|
|
@@ -429,24 +378,20 @@ export async function buildProviderCreatePlan(
|
|
|
429
378
|
options: CreateResolvedOptions,
|
|
430
379
|
cwd: string,
|
|
431
380
|
): Promise<ProviderCreatePlan> {
|
|
432
|
-
const
|
|
433
|
-
options.preset === "monorepo" ?
|
|
434
|
-
if (options.preset === "monorepo" && !
|
|
381
|
+
const resolvedWorkspaceRoot =
|
|
382
|
+
options.preset === "monorepo" ? findApifuseInternalWorkspaceRoot(cwd) : undefined;
|
|
383
|
+
if (options.preset === "monorepo" && !resolvedWorkspaceRoot) {
|
|
435
384
|
throw new Error(
|
|
436
|
-
"Monorepo preset
|
|
385
|
+
"Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
|
|
437
386
|
);
|
|
438
387
|
}
|
|
439
|
-
const resolvedWorkspaceRoot =
|
|
440
|
-
options.preset === "monorepo" ? workspaceRoot : undefined;
|
|
441
388
|
let providerRoot: string;
|
|
442
389
|
let installCwd: string;
|
|
443
390
|
|
|
444
391
|
if (options.outputDir) {
|
|
445
392
|
providerRoot = resolve(cwd, options.outputDir);
|
|
446
393
|
installCwd =
|
|
447
|
-
options.preset === "monorepo" && resolvedWorkspaceRoot
|
|
448
|
-
? resolvedWorkspaceRoot
|
|
449
|
-
: providerRoot;
|
|
394
|
+
options.preset === "monorepo" && resolvedWorkspaceRoot ? resolvedWorkspaceRoot : providerRoot;
|
|
450
395
|
} else if (options.preset === "monorepo" && resolvedWorkspaceRoot) {
|
|
451
396
|
providerRoot = resolve(resolvedWorkspaceRoot, "providers", options.name);
|
|
452
397
|
installCwd = resolvedWorkspaceRoot;
|
|
@@ -459,33 +404,101 @@ export async function buildProviderCreatePlan(
|
|
|
459
404
|
throw new Error(`Target directory already exists: ${providerRoot}`);
|
|
460
405
|
}
|
|
461
406
|
|
|
407
|
+
if (options.sdkSpecifier?.startsWith("workspace:") && !resolvedWorkspaceRoot) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
"workspace:* is only valid inside the APIFuse monorepo because public Provider SDK scaffolds must install from npm or an explicit tarball/file specifier.",
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
462
413
|
const sdkSpecifier =
|
|
463
414
|
options.sdkSpecifier ??
|
|
464
|
-
(options.preset === "monorepo"
|
|
415
|
+
(options.preset === "monorepo" && resolvedWorkspaceRoot
|
|
416
|
+
? "workspace:*"
|
|
417
|
+
: `^${packageJson.version}`);
|
|
465
418
|
const relativeProviderRoot = relative(cwd, providerRoot) || options.name;
|
|
466
419
|
const nextDevCommand = `cd ${relativeProviderRoot} && bun run dev`;
|
|
467
420
|
const packageName =
|
|
468
421
|
options.preset === "monorepo"
|
|
469
422
|
? `@apifuse/provider-${options.name}`
|
|
470
423
|
: `apifuse-provider-${options.name}`;
|
|
424
|
+
const templateValues = {
|
|
425
|
+
PROVIDER_ID: options.name,
|
|
426
|
+
DISPLAY_NAME: escapeTemplate(options.displayName),
|
|
427
|
+
CATEGORY: options.category,
|
|
428
|
+
RUNTIME: options.runtime,
|
|
429
|
+
BROWSER_BLOCK:
|
|
430
|
+
options.runtime === "browser"
|
|
431
|
+
? ',\n browser: {\n engine: "playwright-stealth",\n }'
|
|
432
|
+
: "",
|
|
433
|
+
SECRETS_BLOCK: renderSecretsBlock(options.authMode),
|
|
434
|
+
CREDENTIAL_BLOCK: renderCredentialBlock(options.authMode),
|
|
435
|
+
AUTH_BLOCK: renderAuthBlock(options.authMode),
|
|
436
|
+
};
|
|
471
437
|
|
|
472
438
|
const files: ProviderPlanFile[] = [
|
|
439
|
+
{
|
|
440
|
+
path: resolve(providerRoot, ".dockerignore"),
|
|
441
|
+
content: await renderTemplate(".dockerignore.tpl", {}),
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
path: resolve(providerRoot, ".gitignore"),
|
|
445
|
+
content: await renderTemplate(".gitignore.tpl", {}),
|
|
446
|
+
},
|
|
473
447
|
{
|
|
474
448
|
path: resolve(providerRoot, "index.ts"),
|
|
475
|
-
content: await renderTemplate("index.ts.tpl",
|
|
449
|
+
content: await renderTemplate("index.ts.tpl", templateValues),
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
path: resolve(providerRoot, "meta.ts"),
|
|
453
|
+
content: await renderTemplate("meta.ts.tpl", {
|
|
476
454
|
PROVIDER_ID: options.name,
|
|
477
455
|
DISPLAY_NAME: escapeTemplate(options.displayName),
|
|
478
456
|
CATEGORY: options.category,
|
|
479
|
-
|
|
480
|
-
|
|
457
|
+
}),
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
path: resolve(providerRoot, "operations", "index.ts"),
|
|
461
|
+
content: await renderTemplate("operations/index.ts.tpl", {}),
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
path: resolve(providerRoot, "operations", "ping.ts"),
|
|
465
|
+
content: await renderTemplate("operations/ping.ts.tpl", {
|
|
466
|
+
DISPLAY_NAME: escapeTemplate(options.displayName),
|
|
467
|
+
HANDLER_CTX: options.runtime === "browser" ? "ctx" : "_ctx",
|
|
468
|
+
BROWSER_HANDLER_BLOCK:
|
|
469
|
+
options.runtime === "browser"
|
|
470
|
+
? '\n const page = await ctx.browser.newPage();\n await page.goto("https://example.com");\n const title = await page.title();\n const frames = await page.frames();\n await page.close();\n'
|
|
471
|
+
: "",
|
|
472
|
+
BROWSER_RESPONSE_FIELDS:
|
|
481
473
|
options.runtime === "browser"
|
|
482
|
-
?
|
|
474
|
+
? ",\n pageTitle: title,\n frameCount: frames.length"
|
|
483
475
|
: "",
|
|
484
|
-
SECRETS_BLOCK: renderSecretsBlock(options.authMode),
|
|
485
|
-
CREDENTIAL_BLOCK: renderCredentialBlock(options.authMode),
|
|
486
|
-
AUTH_BLOCK: renderAuthBlock(options.authMode),
|
|
487
476
|
}),
|
|
488
477
|
},
|
|
478
|
+
{
|
|
479
|
+
path: resolve(providerRoot, "schemas", "ping.ts"),
|
|
480
|
+
content: await renderTemplate("schemas/ping.ts.tpl", {}),
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
path: resolve(providerRoot, "upstream", "README.md"),
|
|
484
|
+
content: await renderTemplate("upstream/README.md.tpl", {}),
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
path: resolve(providerRoot, "mappers", "README.md"),
|
|
488
|
+
content: await renderTemplate("mappers/README.md.tpl", {}),
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
path: resolve(providerRoot, "domain", "README.md"),
|
|
492
|
+
content: await renderTemplate("domain/README.md.tpl", {}),
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
path: resolve(providerRoot, "locales", "en.json"),
|
|
496
|
+
content: renderStarterLocaleCatalog(options.displayName, "en"),
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
path: resolve(providerRoot, "locales", "ko.json"),
|
|
500
|
+
content: renderStarterLocaleCatalog(options.displayName, "ko"),
|
|
501
|
+
},
|
|
489
502
|
{
|
|
490
503
|
path: resolve(providerRoot, "package.json"),
|
|
491
504
|
content: renderPackageJson({
|
|
@@ -538,15 +551,17 @@ export async function buildProviderCreatePlan(
|
|
|
538
551
|
packageName,
|
|
539
552
|
preset: options.preset,
|
|
540
553
|
providerRoot,
|
|
541
|
-
validationCommands: [
|
|
554
|
+
validationCommands: [
|
|
555
|
+
"bun run check",
|
|
556
|
+
"bun run type-check",
|
|
557
|
+
"bun run submit-check -- --smoke",
|
|
558
|
+
"bun run test",
|
|
559
|
+
],
|
|
542
560
|
workspaceRoot: resolvedWorkspaceRoot,
|
|
543
561
|
};
|
|
544
562
|
}
|
|
545
563
|
|
|
546
|
-
async function renderTemplate(
|
|
547
|
-
fileName: string,
|
|
548
|
-
values: Record<string, string>,
|
|
549
|
-
): Promise<string> {
|
|
564
|
+
async function renderTemplate(fileName: string, values: Record<string, string>): Promise<string> {
|
|
550
565
|
const templatePath = resolve(TEMPLATE_DIR, fileName);
|
|
551
566
|
const template = await readFile(templatePath, "utf8");
|
|
552
567
|
return template.replace(/\{\{([A-Z_]+)\}\}/g, (_match, key: string) => {
|
|
@@ -554,10 +569,7 @@ async function renderTemplate(
|
|
|
554
569
|
});
|
|
555
570
|
}
|
|
556
571
|
|
|
557
|
-
function renderPackageJson(input: {
|
|
558
|
-
packageName: string;
|
|
559
|
-
sdkSpecifier: string;
|
|
560
|
-
}): string {
|
|
572
|
+
function renderPackageJson(input: { packageName: string; sdkSpecifier: string }): string {
|
|
561
573
|
return `${JSON.stringify(
|
|
562
574
|
{
|
|
563
575
|
name: input.packageName,
|
|
@@ -567,10 +579,11 @@ function renderPackageJson(input: {
|
|
|
567
579
|
main: "./index.ts",
|
|
568
580
|
scripts: {
|
|
569
581
|
dev: "apifuse dev .",
|
|
570
|
-
check: "apifuse check .",
|
|
582
|
+
check: "apifuse check . && bun run type-check",
|
|
583
|
+
"type-check": "tsc --noEmit",
|
|
584
|
+
"submit-check": "apifuse submit-check . --markdown submission-report.md",
|
|
571
585
|
test: "apifuse test .",
|
|
572
586
|
record: "apifuse record .",
|
|
573
|
-
"perf:sample": "apifuse perf . --operation ping --runs 3",
|
|
574
587
|
start: "bun start.ts",
|
|
575
588
|
},
|
|
576
589
|
dependencies: {
|
|
@@ -578,6 +591,7 @@ function renderPackageJson(input: {
|
|
|
578
591
|
},
|
|
579
592
|
devDependencies: {
|
|
580
593
|
"@types/bun": "latest",
|
|
594
|
+
typescript: "^6.0.3",
|
|
581
595
|
},
|
|
582
596
|
},
|
|
583
597
|
null,
|
|
@@ -596,6 +610,7 @@ function renderTsconfig(): string {
|
|
|
596
610
|
noEmit: true,
|
|
597
611
|
skipLibCheck: true,
|
|
598
612
|
resolveJsonModule: true,
|
|
613
|
+
types: ["bun"],
|
|
599
614
|
},
|
|
600
615
|
include: ["**/*.ts"],
|
|
601
616
|
exclude: ["node_modules"],
|
|
@@ -605,6 +620,61 @@ function renderTsconfig(): string {
|
|
|
605
620
|
)}\n`;
|
|
606
621
|
}
|
|
607
622
|
|
|
623
|
+
function renderStarterLocaleCatalog(displayName: string, locale: "en" | "ko"): string {
|
|
624
|
+
const catalog = {
|
|
625
|
+
meta: {
|
|
626
|
+
displayName,
|
|
627
|
+
description:
|
|
628
|
+
locale === "ko"
|
|
629
|
+
? `${displayName} APIFuse 커뮤니티 기여용 provider starter입니다.`
|
|
630
|
+
: `${displayName} provider starter for APIFuse community contributions.`,
|
|
631
|
+
},
|
|
632
|
+
operations: {
|
|
633
|
+
ping: {
|
|
634
|
+
description:
|
|
635
|
+
locale === "ko"
|
|
636
|
+
? "생성된 provider wiring이 APIFuse runtime contract를 통해 작은 샘플 payload를 정상적으로 round-trip하는지 확인합니다. 로컬 개발, baseline check, 첫 bounty scaffold 검증에 사용합니다. production data retrieval이나 upstream-specific workflow에는 사용하지 마세요. 이 starter operation은 생성된 프로젝트가 compile, serve, input/output round-trip을 수행하는지 증명하기 위한 용도입니다."
|
|
637
|
+
: "Confirms the generated provider wiring is operational by echoing a small sample payload through the APIFuse runtime contract. Use when validating local development, baseline checks, or first-pass bounty scaffolds. Do NOT use for production data retrieval or upstream-specific workflows because this starter operation exists only to prove the generated project compiles, serves, and round-trips input/output correctly.",
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
schemaDescriptions: {
|
|
641
|
+
input: {
|
|
642
|
+
root:
|
|
643
|
+
locale === "ko"
|
|
644
|
+
? "생성된 ping operation의 입력 payload"
|
|
645
|
+
: "Input payload for the generated ping operation.",
|
|
646
|
+
value:
|
|
647
|
+
locale === "ko"
|
|
648
|
+
? "생성된 provider scaffold wiring 검증에 사용하는 샘플 입력값"
|
|
649
|
+
: "Sample input value used to verify the generated provider scaffold is wired correctly.",
|
|
650
|
+
},
|
|
651
|
+
output: {
|
|
652
|
+
root:
|
|
653
|
+
locale === "ko"
|
|
654
|
+
? "생성된 ping operation이 반환하는 출력 payload"
|
|
655
|
+
: "Output payload returned by the generated ping operation.",
|
|
656
|
+
ok:
|
|
657
|
+
locale === "ko"
|
|
658
|
+
? "생성된 provider가 샘플 요청을 성공적으로 처리했는지 여부"
|
|
659
|
+
: "Whether the generated provider handled the sample request successfully.",
|
|
660
|
+
message:
|
|
661
|
+
locale === "ko"
|
|
662
|
+
? "생성된 provider가 샘플 payload를 round-trip했음을 보여주는 사람이 읽을 수 있는 확인 메시지"
|
|
663
|
+
: "Human-readable confirmation that the generated provider round-tripped the sample payload.",
|
|
664
|
+
pageTitle:
|
|
665
|
+
locale === "ko"
|
|
666
|
+
? "browser 런타임 provider일 때 로드된 페이지의 제목 (해당되지 않으면 생략)"
|
|
667
|
+
: "Title of the loaded page when the provider uses the browser runtime; omitted otherwise.",
|
|
668
|
+
frameCount:
|
|
669
|
+
locale === "ko"
|
|
670
|
+
? "browser 런타임 provider일 때 로드된 페이지의 frame 개수 (해당되지 않으면 생략)"
|
|
671
|
+
: "Number of frames in the loaded page when the provider uses the browser runtime; omitted otherwise.",
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
};
|
|
675
|
+
return `${JSON.stringify(catalog, null, 2)}\n`;
|
|
676
|
+
}
|
|
677
|
+
|
|
608
678
|
function renderAuthBlock(authMode: CreateAuthMode): string {
|
|
609
679
|
switch (authMode) {
|
|
610
680
|
case "none":
|
|
@@ -615,8 +685,8 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
|
|
|
615
685
|
return `{
|
|
616
686
|
mode: "credentials",
|
|
617
687
|
flow: {
|
|
618
|
-
start: async () => ({
|
|
619
|
-
kind: "
|
|
688
|
+
start: async (_ctx) => ({
|
|
689
|
+
kind: "form",
|
|
620
690
|
turnId: crypto.randomUUID(),
|
|
621
691
|
expectedInput: {
|
|
622
692
|
schema: {
|
|
@@ -642,13 +712,24 @@ function renderAuthBlock(authMode: CreateAuthMode): string {
|
|
|
642
712
|
},
|
|
643
713
|
hint: "Generated placeholder credential flow completed. Replace this with real auth logic.",
|
|
644
714
|
}),
|
|
715
|
+
refresh: async () => ({
|
|
716
|
+
kind: "complete",
|
|
717
|
+
turnId: crypto.randomUUID(),
|
|
718
|
+
data: {
|
|
719
|
+
credential: {
|
|
720
|
+
username: "replace-with-refreshed-username",
|
|
721
|
+
password: "replace-with-refreshed-password",
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
hint: "Return refreshed credential data here, or throw AuthError with code AUTH_REQUIRED when silent refresh is not possible.",
|
|
725
|
+
}),
|
|
645
726
|
},
|
|
646
727
|
}`;
|
|
647
728
|
case "oauth2":
|
|
648
729
|
return `{
|
|
649
730
|
mode: "oauth2",
|
|
650
731
|
flow: {
|
|
651
|
-
start: async () => ({
|
|
732
|
+
start: async (_ctx) => ({
|
|
652
733
|
kind: "redirect",
|
|
653
734
|
turnId: crypto.randomUUID(),
|
|
654
735
|
data: {
|
|
@@ -713,11 +794,11 @@ export function toDisplayName(name: string): string {
|
|
|
713
794
|
.join(" ");
|
|
714
795
|
}
|
|
715
796
|
|
|
716
|
-
function
|
|
797
|
+
function findApifuseInternalWorkspaceRoot(cwd: string): string | undefined {
|
|
717
798
|
let currentDirectory = cwd;
|
|
718
799
|
|
|
719
800
|
while (true) {
|
|
720
|
-
if (
|
|
801
|
+
if (isApifuseInternalWorkspaceRoot(currentDirectory)) {
|
|
721
802
|
return currentDirectory;
|
|
722
803
|
}
|
|
723
804
|
|
|
@@ -730,6 +811,29 @@ function findWorkspaceRoot(cwd: string): string | undefined {
|
|
|
730
811
|
}
|
|
731
812
|
}
|
|
732
813
|
|
|
814
|
+
function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
|
|
815
|
+
const providerSdkPackageJsonPath = resolve(
|
|
816
|
+
workspaceRoot,
|
|
817
|
+
"packages",
|
|
818
|
+
"provider-sdk",
|
|
819
|
+
"package.json",
|
|
820
|
+
);
|
|
821
|
+
if (!existsSync(providerSdkPackageJsonPath)) {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
const packageJson = JSON.parse(readFileSync(providerSdkPackageJsonPath, "utf8"));
|
|
826
|
+
return (
|
|
827
|
+
typeof packageJson === "object" &&
|
|
828
|
+
packageJson !== null &&
|
|
829
|
+
"name" in packageJson &&
|
|
830
|
+
packageJson.name === "@apifuse/provider-sdk"
|
|
831
|
+
);
|
|
832
|
+
} catch {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
733
837
|
async function writePlan(plan: ProviderCreatePlan): Promise<void> {
|
|
734
838
|
for (const file of plan.files) {
|
|
735
839
|
await mkdir(dirname(file.path), { recursive: true });
|
|
@@ -737,27 +841,17 @@ async function writePlan(plan: ProviderCreatePlan): Promise<void> {
|
|
|
737
841
|
}
|
|
738
842
|
}
|
|
739
843
|
|
|
740
|
-
async function installDependencies(
|
|
741
|
-
plan: ProviderCreatePlan,
|
|
742
|
-
jsonMode: boolean,
|
|
743
|
-
): Promise<void> {
|
|
844
|
+
async function installDependencies(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
|
|
744
845
|
await runCommand(plan.installCommand, plan.installCwd, jsonMode);
|
|
745
846
|
}
|
|
746
847
|
|
|
747
|
-
async function runBaselineValidation(
|
|
748
|
-
plan: ProviderCreatePlan,
|
|
749
|
-
jsonMode: boolean,
|
|
750
|
-
): Promise<void> {
|
|
848
|
+
async function runBaselineValidation(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
|
|
751
849
|
for (const command of plan.validationCommands) {
|
|
752
850
|
await runCommand(command, plan.providerRoot, jsonMode);
|
|
753
851
|
}
|
|
754
852
|
}
|
|
755
853
|
|
|
756
|
-
async function runCommand(
|
|
757
|
-
command: string,
|
|
758
|
-
cwd: string,
|
|
759
|
-
jsonMode: boolean,
|
|
760
|
-
): Promise<void> {
|
|
854
|
+
async function runCommand(command: string, cwd: string, jsonMode: boolean): Promise<void> {
|
|
761
855
|
const [binary, ...args] = command.split(" ");
|
|
762
856
|
if (!binary) {
|
|
763
857
|
throw new Error(`Cannot run empty command in ${cwd}`);
|
|
@@ -789,9 +883,7 @@ async function runCommand(
|
|
|
789
883
|
rejectPromise(
|
|
790
884
|
new Error(
|
|
791
885
|
`Command failed (${command}) in ${cwd}${
|
|
792
|
-
stdout || stderr
|
|
793
|
-
? `\n${[stdout, stderr].filter(Boolean).join("\n")}`
|
|
794
|
-
: ""
|
|
886
|
+
stdout || stderr ? `\n${[stdout, stderr].filter(Boolean).join("\n")}` : ""
|
|
795
887
|
}`,
|
|
796
888
|
),
|
|
797
889
|
);
|
|
@@ -799,11 +891,7 @@ async function runCommand(
|
|
|
799
891
|
});
|
|
800
892
|
}
|
|
801
893
|
|
|
802
|
-
function printResult(
|
|
803
|
-
plan: ProviderCreatePlan,
|
|
804
|
-
jsonMode: boolean,
|
|
805
|
-
dryRun: boolean,
|
|
806
|
-
) {
|
|
894
|
+
function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolean) {
|
|
807
895
|
const payload = {
|
|
808
896
|
success: true,
|
|
809
897
|
dryRun,
|
|
@@ -820,9 +908,7 @@ function printResult(
|
|
|
820
908
|
},
|
|
821
909
|
validationCommands: plan.validationCommands,
|
|
822
910
|
nextDevCommand: plan.nextDevCommand,
|
|
823
|
-
files: plan.files.map(
|
|
824
|
-
(file) => relative(plan.providerRoot, file.path) || file.path,
|
|
825
|
-
),
|
|
911
|
+
files: plan.files.map((file) => relative(plan.providerRoot, file.path) || file.path),
|
|
826
912
|
};
|
|
827
913
|
|
|
828
914
|
if (jsonMode) {
|
|
@@ -837,6 +923,14 @@ function printResult(
|
|
|
837
923
|
console.log(`Validation: (cd ${plan.providerRoot} && ${command})`);
|
|
838
924
|
}
|
|
839
925
|
console.log(`Next local dev: ${plan.nextDevCommand}`);
|
|
926
|
+
console.log(
|
|
927
|
+
"Submission evidence: run `bun run submit-check -- --smoke` to archive measured `/health` and `POST /v1/{operation}` results.",
|
|
928
|
+
);
|
|
929
|
+
if (plan.files.some((file) => file.content.includes('runtime: "browser"'))) {
|
|
930
|
+
console.log(
|
|
931
|
+
"Browser runtime: run `bunx playwright install chromium` locally or set `APIFUSE__CDP_POOL__URL` before browser-backed smoke tests.",
|
|
932
|
+
);
|
|
933
|
+
}
|
|
840
934
|
}
|
|
841
935
|
|
|
842
936
|
function escapeTemplate(value: string): string {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
.git/
|
|
3
|
+
.github/
|
|
4
|
+
|
|
5
|
+
# Environment and local secrets
|
|
6
|
+
.env
|
|
7
|
+
.env.*
|
|
8
|
+
!.env.example
|
|
9
|
+
|
|
10
|
+
# Local reports and generated artifacts
|
|
11
|
+
submission-report.md
|
|
12
|
+
coverage/
|
|
13
|
+
dist/
|
|
14
|
+
.cache/
|
|
15
|
+
.turbo/
|
|
16
|
+
.bun/
|
|
17
|
+
*.tsbuildinfo
|
|
18
|
+
|
|
19
|
+
# OS/editor junk
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
*.swp
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
|
|
3
|
+
# Environment and local secrets
|
|
4
|
+
.env
|
|
5
|
+
.env.*
|
|
6
|
+
!.env.example
|
|
7
|
+
|
|
8
|
+
# Build, coverage, cache, and local runtime artifacts
|
|
9
|
+
coverage/
|
|
10
|
+
dist/
|
|
11
|
+
.cache/
|
|
12
|
+
.turbo/
|
|
13
|
+
.bun/
|
|
14
|
+
*.tsbuildinfo
|
|
15
|
+
|
|
16
|
+
# Bounty submission output
|
|
17
|
+
submission-report.md
|
|
18
|
+
|
|
19
|
+
# OS/editor junk
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
*.swp
|