@apifuse/provider-sdk 2.1.0-beta.18 → 2.1.0-beta.19

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.
@@ -3,7 +3,7 @@ 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
- import { cancel, intro, isCancel, note, outro, select, text, } from "@clack/prompts";
6
+ import { cancel, intro, isCancel, note, outro, select, text } from "@clack/prompts";
7
7
  import { z } from "zod";
8
8
  import packageJson from "../../package.json";
9
9
  export const PROVIDER_NAME_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
@@ -17,12 +17,7 @@ export const CATEGORY_OPTIONS = [
17
17
  "communication",
18
18
  "other",
19
19
  ];
20
- export const AUTH_MODE_OPTIONS = [
21
- "none",
22
- "platform-managed",
23
- "credentials",
24
- "oauth2",
25
- ];
20
+ export const AUTH_MODE_OPTIONS = ["none", "platform-managed", "credentials", "oauth2"];
26
21
  export const RUNTIME_OPTIONS = ["standard", "browser"];
27
22
  export const PRESET_OPTIONS = ["standalone", "monorepo"];
28
23
  const CREATE_CONFIG_SCHEMA = z.object({
@@ -64,9 +59,7 @@ export async function main() {
64
59
  return;
65
60
  }
66
61
  const parsed = parseArgs(normalizedArgs);
67
- const config = parsed.configPath
68
- ? await loadConfig(parsed.configPath)
69
- : undefined;
62
+ const config = parsed.configPath ? await loadConfig(parsed.configPath) : undefined;
70
63
  const resolved = await resolveCreateOptions(parsed, config, process.cwd());
71
64
  const plan = await buildProviderCreatePlan(resolved, process.cwd());
72
65
  if (resolved.dryRun) {
@@ -174,9 +167,7 @@ async function resolveCreateOptions(parsed, config, cwd) {
174
167
  category: parsed.category ?? config?.category,
175
168
  authMode: parsed.authMode ?? config?.authMode,
176
169
  runtime: parsed.runtime ?? config?.runtime,
177
- sdkSpecifier: parsed.sdkSpecifier ??
178
- config?.sdkSpecifier ??
179
- process.env.APIFUSE__SDK__SPECIFIER,
170
+ sdkSpecifier: parsed.sdkSpecifier ?? config?.sdkSpecifier ?? process.env.APIFUSE__SDK__SPECIFIER,
180
171
  dryRun: parsed.dryRun,
181
172
  json: parsed.json,
182
173
  yes: parsed.yes,
@@ -267,9 +258,7 @@ async function promptValue(prompt) {
267
258
  return result;
268
259
  }
269
260
  export async function buildProviderCreatePlan(options, cwd) {
270
- const resolvedWorkspaceRoot = options.preset === "monorepo"
271
- ? findApifuseInternalWorkspaceRoot(cwd)
272
- : undefined;
261
+ const resolvedWorkspaceRoot = options.preset === "monorepo" ? findApifuseInternalWorkspaceRoot(cwd) : undefined;
273
262
  if (options.preset === "monorepo" && !resolvedWorkspaceRoot) {
274
263
  throw new Error("Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.");
275
264
  }
@@ -278,9 +267,7 @@ export async function buildProviderCreatePlan(options, cwd) {
278
267
  if (options.outputDir) {
279
268
  providerRoot = resolve(cwd, options.outputDir);
280
269
  installCwd =
281
- options.preset === "monorepo" && resolvedWorkspaceRoot
282
- ? resolvedWorkspaceRoot
283
- : providerRoot;
270
+ options.preset === "monorepo" && resolvedWorkspaceRoot ? resolvedWorkspaceRoot : providerRoot;
284
271
  }
285
272
  else if (options.preset === "monorepo" && resolvedWorkspaceRoot) {
286
273
  providerRoot = resolve(resolvedWorkspaceRoot, "providers", options.name);
@@ -293,8 +280,7 @@ export async function buildProviderCreatePlan(options, cwd) {
293
280
  if (existsSync(providerRoot)) {
294
281
  throw new Error(`Target directory already exists: ${providerRoot}`);
295
282
  }
296
- if (options.sdkSpecifier?.startsWith("workspace:") &&
297
- !resolvedWorkspaceRoot) {
283
+ if (options.sdkSpecifier?.startsWith("workspace:") && !resolvedWorkspaceRoot) {
298
284
  throw new Error("workspace:* is only valid inside the APIFuse monorepo because public Provider SDK scaffolds must install from npm or an explicit tarball/file specifier.");
299
285
  }
300
286
  const sdkSpecifier = options.sdkSpecifier ??
@@ -434,7 +420,7 @@ export async function buildProviderCreatePlan(options, cwd) {
434
420
  validationCommands: [
435
421
  "bun run check",
436
422
  "bun run type-check",
437
- "bun run submit-check",
423
+ "bun run submit-check -- --smoke",
438
424
  "bun run test",
439
425
  ],
440
426
  workspaceRoot: resolvedWorkspaceRoot,
@@ -715,9 +701,7 @@ async function runCommand(command, cwd, jsonMode) {
715
701
  resolvePromise();
716
702
  return;
717
703
  }
718
- rejectPromise(new Error(`Command failed (${command}) in ${cwd}${stdout || stderr
719
- ? `\n${[stdout, stderr].filter(Boolean).join("\n")}`
720
- : ""}`));
704
+ rejectPromise(new Error(`Command failed (${command}) in ${cwd}${stdout || stderr ? `\n${[stdout, stderr].filter(Boolean).join("\n")}` : ""}`));
721
705
  });
722
706
  });
723
707
  }
@@ -751,7 +735,7 @@ function printResult(plan, jsonMode, dryRun) {
751
735
  console.log(`Validation: (cd ${plan.providerRoot} && ${command})`);
752
736
  }
753
737
  console.log(`Next local dev: ${plan.nextDevCommand}`);
754
- console.log("Submission evidence: run `bun run submit-check`, save the generated report, and note `/health` plus `POST /v1/{operation}` smoke results.");
738
+ console.log("Submission evidence: run `bun run submit-check -- --smoke` to archive measured `/health` and `POST /v1/{operation}` results.");
755
739
  if (plan.files.some((file) => file.content.includes('runtime: "browser"'))) {
756
740
  console.log("Browser runtime: run `bunx playwright install chromium` locally or set `APIFUSE__CDP_POOL__URL` before browser-backed smoke tests.");
757
741
  }
@@ -8,7 +8,7 @@ Generated with `apifuse create`.
8
8
  bun run dev
9
9
  bun run check
10
10
  bun run test
11
- bun run submit-check
11
+ bun run submit-check -- --smoke
12
12
  bunx apifuse perf . --operation <operation-id> --runs 3
13
13
  ```
14
14
 
@@ -35,15 +35,17 @@ review when `index.ts` remains a short composition root.
35
35
  Before posting bounty evidence, run:
36
36
 
37
37
  ```bash
38
- bun run submit-check
38
+ bun run submit-check -- --smoke
39
39
  ```
40
40
 
41
41
  This writes `submission-report.md` with a review-readiness score, blockers,
42
42
  warnings, health coverage notes, fixture/schema evidence, and remediation. A
43
43
  score is not a payout guarantee; blockers must be fixed before maintainer
44
44
  review. The generated `ping` starter intentionally warns until you replace it
45
- with real upstream-backed Operations. The full public-only checklist is shipped
46
- in `node_modules/@apifuse/provider-sdk/SUBMISSION.md`.
45
+ with real upstream-backed Operations. `APIFUSE__PROVIDER__*` env vars enable
46
+ live upstream calls; without them, structured provider errors can still verify
47
+ runtime routing. The full public-only checklist is shipped in
48
+ `node_modules/@apifuse/provider-sdk/SUBMISSION.md`.
47
49
 
48
50
 
49
51
  ## Operation guide
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.1.0-beta.18",
2
+ "version": "2.1.0-beta.19",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
package/src/cli/create.ts CHANGED
@@ -4,15 +4,7 @@ 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,9 +82,7 @@ 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
@@ -126,9 +111,7 @@ export async function main() {
126
111
  }
127
112
 
128
113
  const parsed = parseArgs(normalizedArgs);
129
- const config = parsed.configPath
130
- ? await loadConfig(parsed.configPath)
131
- : undefined;
114
+ const config = parsed.configPath ? await loadConfig(parsed.configPath) : undefined;
132
115
  const resolved = await resolveCreateOptions(parsed, config, process.cwd());
133
116
  const plan = await buildProviderCreatePlan(resolved, process.cwd());
134
117
 
@@ -195,18 +178,10 @@ function parseArgs(argv: string[]): ParsedArgs {
195
178
  parsed.displayName = ensureValue(flag, consumeValue());
196
179
  break;
197
180
  case "--category":
198
- parsed.category = parseEnum(
199
- "category",
200
- consumeValue(),
201
- CATEGORY_OPTIONS,
202
- );
181
+ parsed.category = parseEnum("category", consumeValue(), CATEGORY_OPTIONS);
203
182
  break;
204
183
  case "--auth-mode":
205
- parsed.authMode = parseEnum(
206
- "auth mode",
207
- consumeValue(),
208
- AUTH_MODE_OPTIONS,
209
- );
184
+ parsed.authMode = parseEnum("auth mode", consumeValue(), AUTH_MODE_OPTIONS);
210
185
  break;
211
186
  case "--runtime":
212
187
  parsed.runtime = parseEnum("runtime", consumeValue(), RUNTIME_OPTIONS);
@@ -253,9 +228,7 @@ function parseEnum<T extends readonly string[]>(
253
228
  const resolvedValue = ensureValue(`--${label}`, value);
254
229
  const matchedValue = options.find((option) => option === resolvedValue);
255
230
  if (!matchedValue) {
256
- throw new Error(
257
- `Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`,
258
- );
231
+ throw new Error(`Invalid ${label}: ${resolvedValue}. Expected one of: ${options.join(", ")}`);
259
232
  }
260
233
  return matchedValue;
261
234
  }
@@ -282,9 +255,7 @@ async function resolveCreateOptions(
282
255
  authMode: parsed.authMode ?? config?.authMode,
283
256
  runtime: parsed.runtime ?? config?.runtime,
284
257
  sdkSpecifier:
285
- parsed.sdkSpecifier ??
286
- config?.sdkSpecifier ??
287
- process.env.APIFUSE__SDK__SPECIFIER,
258
+ parsed.sdkSpecifier ?? config?.sdkSpecifier ?? process.env.APIFUSE__SDK__SPECIFIER,
288
259
  dryRun: parsed.dryRun,
289
260
  json: parsed.json,
290
261
  yes: parsed.yes,
@@ -298,9 +269,7 @@ async function resolveCreateOptions(
298
269
 
299
270
  if (partial.yes) {
300
271
  if (!partial.name) {
301
- throw new Error(
302
- "--yes requires a provider name (positional or via config).",
303
- );
272
+ throw new Error("--yes requires a provider name (positional or via config).");
304
273
  }
305
274
 
306
275
  return {
@@ -410,9 +379,7 @@ export async function buildProviderCreatePlan(
410
379
  cwd: string,
411
380
  ): Promise<ProviderCreatePlan> {
412
381
  const resolvedWorkspaceRoot =
413
- options.preset === "monorepo"
414
- ? findApifuseInternalWorkspaceRoot(cwd)
415
- : undefined;
382
+ options.preset === "monorepo" ? findApifuseInternalWorkspaceRoot(cwd) : undefined;
416
383
  if (options.preset === "monorepo" && !resolvedWorkspaceRoot) {
417
384
  throw new Error(
418
385
  "Monorepo preset is internal to the APIFuse repository. External bounty workspaces are one-provider repositories; use the standalone default create flow.",
@@ -424,9 +391,7 @@ export async function buildProviderCreatePlan(
424
391
  if (options.outputDir) {
425
392
  providerRoot = resolve(cwd, options.outputDir);
426
393
  installCwd =
427
- options.preset === "monorepo" && resolvedWorkspaceRoot
428
- ? resolvedWorkspaceRoot
429
- : providerRoot;
394
+ options.preset === "monorepo" && resolvedWorkspaceRoot ? resolvedWorkspaceRoot : providerRoot;
430
395
  } else if (options.preset === "monorepo" && resolvedWorkspaceRoot) {
431
396
  providerRoot = resolve(resolvedWorkspaceRoot, "providers", options.name);
432
397
  installCwd = resolvedWorkspaceRoot;
@@ -439,10 +404,7 @@ export async function buildProviderCreatePlan(
439
404
  throw new Error(`Target directory already exists: ${providerRoot}`);
440
405
  }
441
406
 
442
- if (
443
- options.sdkSpecifier?.startsWith("workspace:") &&
444
- !resolvedWorkspaceRoot
445
- ) {
407
+ if (options.sdkSpecifier?.startsWith("workspace:") && !resolvedWorkspaceRoot) {
446
408
  throw new Error(
447
409
  "workspace:* is only valid inside the APIFuse monorepo because public Provider SDK scaffolds must install from npm or an explicit tarball/file specifier.",
448
410
  );
@@ -592,17 +554,14 @@ export async function buildProviderCreatePlan(
592
554
  validationCommands: [
593
555
  "bun run check",
594
556
  "bun run type-check",
595
- "bun run submit-check",
557
+ "bun run submit-check -- --smoke",
596
558
  "bun run test",
597
559
  ],
598
560
  workspaceRoot: resolvedWorkspaceRoot,
599
561
  };
600
562
  }
601
563
 
602
- async function renderTemplate(
603
- fileName: string,
604
- values: Record<string, string>,
605
- ): Promise<string> {
564
+ async function renderTemplate(fileName: string, values: Record<string, string>): Promise<string> {
606
565
  const templatePath = resolve(TEMPLATE_DIR, fileName);
607
566
  const template = await readFile(templatePath, "utf8");
608
567
  return template.replace(/\{\{([A-Z_]+)\}\}/g, (_match, key: string) => {
@@ -610,10 +569,7 @@ async function renderTemplate(
610
569
  });
611
570
  }
612
571
 
613
- function renderPackageJson(input: {
614
- packageName: string;
615
- sdkSpecifier: string;
616
- }): string {
572
+ function renderPackageJson(input: { packageName: string; sdkSpecifier: string }): string {
617
573
  return `${JSON.stringify(
618
574
  {
619
575
  name: input.packageName,
@@ -625,8 +581,7 @@ function renderPackageJson(input: {
625
581
  dev: "apifuse dev .",
626
582
  check: "apifuse check . && bun run type-check",
627
583
  "type-check": "tsc --noEmit",
628
- "submit-check":
629
- "apifuse submit-check . --markdown submission-report.md",
584
+ "submit-check": "apifuse submit-check . --markdown submission-report.md",
630
585
  test: "apifuse test .",
631
586
  record: "apifuse record .",
632
587
  start: "bun start.ts",
@@ -665,10 +620,7 @@ function renderTsconfig(): string {
665
620
  )}\n`;
666
621
  }
667
622
 
668
- function renderStarterLocaleCatalog(
669
- displayName: string,
670
- locale: "en" | "ko",
671
- ): string {
623
+ function renderStarterLocaleCatalog(displayName: string, locale: "en" | "ko"): string {
672
624
  const catalog = {
673
625
  meta: {
674
626
  displayName,
@@ -870,9 +822,7 @@ function isApifuseInternalWorkspaceRoot(workspaceRoot: string): boolean {
870
822
  return false;
871
823
  }
872
824
  try {
873
- const packageJson = JSON.parse(
874
- readFileSync(providerSdkPackageJsonPath, "utf8"),
875
- );
825
+ const packageJson = JSON.parse(readFileSync(providerSdkPackageJsonPath, "utf8"));
876
826
  return (
877
827
  typeof packageJson === "object" &&
878
828
  packageJson !== null &&
@@ -891,27 +841,17 @@ async function writePlan(plan: ProviderCreatePlan): Promise<void> {
891
841
  }
892
842
  }
893
843
 
894
- async function installDependencies(
895
- plan: ProviderCreatePlan,
896
- jsonMode: boolean,
897
- ): Promise<void> {
844
+ async function installDependencies(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
898
845
  await runCommand(plan.installCommand, plan.installCwd, jsonMode);
899
846
  }
900
847
 
901
- async function runBaselineValidation(
902
- plan: ProviderCreatePlan,
903
- jsonMode: boolean,
904
- ): Promise<void> {
848
+ async function runBaselineValidation(plan: ProviderCreatePlan, jsonMode: boolean): Promise<void> {
905
849
  for (const command of plan.validationCommands) {
906
850
  await runCommand(command, plan.providerRoot, jsonMode);
907
851
  }
908
852
  }
909
853
 
910
- async function runCommand(
911
- command: string,
912
- cwd: string,
913
- jsonMode: boolean,
914
- ): Promise<void> {
854
+ async function runCommand(command: string, cwd: string, jsonMode: boolean): Promise<void> {
915
855
  const [binary, ...args] = command.split(" ");
916
856
  if (!binary) {
917
857
  throw new Error(`Cannot run empty command in ${cwd}`);
@@ -943,9 +883,7 @@ async function runCommand(
943
883
  rejectPromise(
944
884
  new Error(
945
885
  `Command failed (${command}) in ${cwd}${
946
- stdout || stderr
947
- ? `\n${[stdout, stderr].filter(Boolean).join("\n")}`
948
- : ""
886
+ stdout || stderr ? `\n${[stdout, stderr].filter(Boolean).join("\n")}` : ""
949
887
  }`,
950
888
  ),
951
889
  );
@@ -953,11 +891,7 @@ async function runCommand(
953
891
  });
954
892
  }
955
893
 
956
- function printResult(
957
- plan: ProviderCreatePlan,
958
- jsonMode: boolean,
959
- dryRun: boolean,
960
- ) {
894
+ function printResult(plan: ProviderCreatePlan, jsonMode: boolean, dryRun: boolean) {
961
895
  const payload = {
962
896
  success: true,
963
897
  dryRun,
@@ -974,9 +908,7 @@ function printResult(
974
908
  },
975
909
  validationCommands: plan.validationCommands,
976
910
  nextDevCommand: plan.nextDevCommand,
977
- files: plan.files.map(
978
- (file) => relative(plan.providerRoot, file.path) || file.path,
979
- ),
911
+ files: plan.files.map((file) => relative(plan.providerRoot, file.path) || file.path),
980
912
  };
981
913
 
982
914
  if (jsonMode) {
@@ -992,7 +924,7 @@ function printResult(
992
924
  }
993
925
  console.log(`Next local dev: ${plan.nextDevCommand}`);
994
926
  console.log(
995
- "Submission evidence: run `bun run submit-check`, save the generated report, and note `/health` plus `POST /v1/{operation}` smoke results.",
927
+ "Submission evidence: run `bun run submit-check -- --smoke` to archive measured `/health` and `POST /v1/{operation}` results.",
996
928
  );
997
929
  if (plan.files.some((file) => file.content.includes('runtime: "browser"'))) {
998
930
  console.log(
@@ -8,7 +8,7 @@ Generated with `apifuse create`.
8
8
  bun run dev
9
9
  bun run check
10
10
  bun run test
11
- bun run submit-check
11
+ bun run submit-check -- --smoke
12
12
  bunx apifuse perf . --operation <operation-id> --runs 3
13
13
  ```
14
14
 
@@ -35,15 +35,17 @@ review when `index.ts` remains a short composition root.
35
35
  Before posting bounty evidence, run:
36
36
 
37
37
  ```bash
38
- bun run submit-check
38
+ bun run submit-check -- --smoke
39
39
  ```
40
40
 
41
41
  This writes `submission-report.md` with a review-readiness score, blockers,
42
42
  warnings, health coverage notes, fixture/schema evidence, and remediation. A
43
43
  score is not a payout guarantee; blockers must be fixed before maintainer
44
44
  review. The generated `ping` starter intentionally warns until you replace it
45
- with real upstream-backed Operations. The full public-only checklist is shipped
46
- in `node_modules/@apifuse/provider-sdk/SUBMISSION.md`.
45
+ with real upstream-backed Operations. `APIFUSE__PROVIDER__*` env vars enable
46
+ live upstream calls; without them, structured provider errors can still verify
47
+ runtime routing. The full public-only checklist is shipped in
48
+ `node_modules/@apifuse/provider-sdk/SUBMISSION.md`.
47
49
 
48
50
 
49
51
  ## Operation guide