@apifuse/provider-sdk 2.2.0-beta.64 → 2.2.0-beta.66

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 CHANGED
@@ -900,8 +900,13 @@ Provider runtime code reaches the outside world through the context only:
900
900
  `ctx.env.get()` / `ctx.credential` for configuration and secrets, `ctx.files` /
901
901
  `ctx.cache` / `ctx.state` for data that must outlive a request. Ambient Node
902
902
  and Bun globals bypass `allowedHosts`, proxy policy, retries, redaction, the
903
- secret presence gate, and telemetry. `apifuse check` reports them (**warn** in
904
- this release; the level is raised once the fleet is clean):
903
+ secret presence gate, and telemetry. `apifuse check` reports them at **error**
904
+ level — a finding fails `apifuse check` and `apifuse submit-check`, in the
905
+ official and the bounty `standalone` mode alike (the level was `warn` for one
906
+ release as a migration window; the window closed once the fleet's request-path
907
+ hits were fixed or acknowledged — a provider that still carries one sees
908
+ `apifuse check` fail on its next SDK bump and fixes or acknowledges it in that
909
+ same PR):
905
910
 
906
911
  - `process-env-direct-read` — `process.env.X`, `process.env["X"]`, a bare
907
912
  `process.env` object use, `Bun.env`. The `APIFUSE__RUNTIME__*` bootstrap
@@ -931,11 +936,14 @@ Scope is runtime source only: tests, recorded fixtures, `.d.ts`, the root
931
936
  `dev.ts` / `start.ts` / `deploy.ts` entrypoints, and the `scripts/`, `tools/`,
932
937
  `bin/` directories at the provider root are not scanned. Keep fixture
933
938
  recorders, smoke scripts, and other operator tooling under `scripts/`, and do
934
- not import from there in request-path modules.
939
+ not import from there in request-path modules. `apifuse check` also leaves out
940
+ any directory that is itself a git checkout (a linked worktree such as
941
+ `.worktree/<branch>/`, a nested clone, an initialised submodule): its files are
942
+ another revision, checked in that checkout, not this provider's runtime source.
935
943
 
936
944
  Acknowledge a deliberate exception on the finding line or the comment-only
937
945
  line above it with `// @apifuse-allow <rule>: <reason>`; the finding then
938
- appears as an `INFO` audit line instead of a warning.
946
+ appears as an `INFO` audit line instead of an error, and the check passes.
939
947
 
940
948
  ```ts
941
949
  // Before: request-path module reads the ambient environment and the disk
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.66
4
+
5
+ - Release candidate for main commit e7f93a6c71e3bca3a680861c0968e22dbe5d1d40.
6
+
7
+ ## 2.2.0-beta.65
8
+
9
+ - Release candidate for main commit 4731ac76145caf85a6c340ad0c446371bd24b052.
10
+
3
11
  ## 2.2.0-beta.64
4
12
 
5
13
  - Release candidate for main commit 0505a938340d08db3ea3e2302cab5f6b5dd2b4e5.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
4
- import { dirname, resolve } from "node:path";
4
+ import { dirname, join, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
7
7
  import { z } from "zod";
@@ -226,6 +226,21 @@ function isScannableProviderSourceFile(relativePath: string): boolean {
226
226
  );
227
227
  }
228
228
 
229
+ /**
230
+ * A directory under the provider root that is itself a git checkout — a linked
231
+ * worktree (`.worktree/<branch>/`, whose `.git` is a file pointing at the main
232
+ * repository), a nested clone (`.git` directory), or an initialised submodule.
233
+ * Its files are another revision of some repository, not this provider's
234
+ * runtime source: an older copy of `upstream/` or `scripts/` there would be
235
+ * linted as if it were request-path code at the root (the tooling-directory
236
+ * exemption is anchored at the provider root), and its `apifuse check` is run
237
+ * in that checkout, not here. Git refuses to track a path named `.git`, so the
238
+ * marker cannot be planted in a committed tree to hide a file from the scan.
239
+ */
240
+ function isNestedCheckout(directory: string): boolean {
241
+ return existsSync(join(directory, ".git"));
242
+ }
243
+
229
244
  function collectProviderSourceFiles(providerRoot: string): Record<string, string> {
230
245
  const sources: Record<string, string> = {};
231
246
  // `.agents`/`.apifuse` are deliberately not skipped: managed content there
@@ -236,7 +251,7 @@ function collectProviderSourceFiles(providerRoot: string): Record<string, string
236
251
  for (const entry of readdirSync(directory, { withFileTypes: true })) {
237
252
  const path = resolve(directory, entry.name);
238
253
  if (entry.isDirectory()) {
239
- if (!skipDirectories.has(entry.name)) {
254
+ if (!skipDirectories.has(entry.name) && !isNestedCheckout(path)) {
240
255
  visit(path);
241
256
  }
242
257
  continue;
@@ -573,12 +588,56 @@ function checkPackageJson(packageJsonPath: string): CheckResult {
573
588
 
574
589
  export const PROVIDER_JSON_CHECK_MESSAGE = "provider.json exists with a valid declaration";
575
590
 
591
+ /**
592
+ * Markets a provider may declare as its primary, and the locales the platform
593
+ * catalogs can carry.
594
+ *
595
+ * These mirror `scripts/provider-deploy/provider-locale-coverage.mjs` in the
596
+ * APIFuse monorepo, which is the policy's source of truth. The duplication is
597
+ * deliberate and bounded: the monorepo cannot be imported from a provider
598
+ * checkout, and a *closed* mirror is the point — it keeps a typo
599
+ * (`primaryMarket: "jp-JP"`) a local failure instead of one that only surfaces
600
+ * in the monorepo's contract validator days later.
601
+ */
602
+ const PROVIDER_PRIMARY_MARKETS = ["jp", "kr", "global"] as const;
603
+ const PROVIDER_COVERAGE_LOCALES = ["en", "ko", "ja"] as const;
604
+ const PROVIDER_BASELINE_LOCALE = "en";
605
+
606
+ /**
607
+ * Declared locale coverage, optional at schemaVersion 1.
608
+ *
609
+ * `apifuse provider declare-locale-coverage` writes this block, and the
610
+ * monorepo declaration schema has accepted it since the rollout landed. This
611
+ * check used to close `provider.json` over four keys, which made the only
612
+ * documented way to clear the monorepo's `LOCALE_COVERAGE_UNDECLARED` finding
613
+ * turn a green `bun run check` red — the declaration was unfixable from either
614
+ * side. The fix is to mirror the monorepo schema rather than to stop
615
+ * validating: the block stays closed over its own two keys so a misspelled
616
+ * field is still caught here, at authoring time.
617
+ */
618
+ const providerI18nDeclarationSchema = z
619
+ .object({
620
+ primaryMarket: z.enum(PROVIDER_PRIMARY_MARKETS),
621
+ locales: z
622
+ .array(z.enum(PROVIDER_COVERAGE_LOCALES))
623
+ .nonempty()
624
+ .refine((locales) => new Set(locales).size === locales.length, {
625
+ message: "i18n.locales must not repeat a locale",
626
+ })
627
+ .refine((locales) => locales.includes(PROVIDER_BASELINE_LOCALE), {
628
+ message: `i18n.locales must include "${PROVIDER_BASELINE_LOCALE}": it is the fallback every other locale resolves through`,
629
+ })
630
+ .optional(),
631
+ })
632
+ .strict();
633
+
576
634
  const providerDeclarationSchema = z
577
635
  .object({
578
636
  schemaVersion: z.literal(1),
579
637
  providerId: z.string(),
580
638
  owner: z.string(),
581
639
  lifecycle: z.enum(["draft", "ready", "live", "retired"]),
640
+ i18n: providerI18nDeclarationSchema.optional(),
582
641
  })
583
642
  .strict();
584
643
 
@@ -609,7 +668,17 @@ function checkProviderJson(providerJsonPath: string, packageJsonPath: string): C
609
668
  return {
610
669
  message: PROVIDER_JSON_CHECK_MESSAGE,
611
670
  passed: true,
612
- details: [`providerId: ${declaration.providerId}`, `lifecycle: ${declaration.lifecycle}`],
671
+ details: [
672
+ `providerId: ${declaration.providerId}`,
673
+ `lifecycle: ${declaration.lifecycle}`,
674
+ ...(declaration.i18n
675
+ ? [
676
+ `locale coverage: primaryMarket ${declaration.i18n.primaryMarket}${
677
+ declaration.i18n.locales ? ` (locales: ${declaration.i18n.locales.join(", ")})` : ""
678
+ }`,
679
+ ]
680
+ : []),
681
+ ],
613
682
  };
614
683
  } catch (error) {
615
684
  return {