@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.51
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 +537 -33
- package/CHANGELOG.md +205 -1
- package/README.md +83 -22
- package/SUBMISSION.md +2 -2
- package/bin/apifuse-check.ts +165 -11
- package/bin/apifuse-dev.ts +57 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-migrate-shape.ts +202 -0
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +57 -2
- package/bin/apifuse-pack-types.ts +357 -38
- package/bin/apifuse-perf.ts +14 -13
- package/bin/apifuse-record.ts +709 -72
- package/bin/apifuse-submit-check.ts +2344 -324
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/submit-check-delimited-text.ts +50 -0
- package/dist/auth-turn/index.d.ts +3 -3
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +67 -0
- package/dist/ceremonies/index.d.ts +16 -0
- package/dist/ceremonies/index.js +141 -36
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +27 -0
- package/dist/cli/create.d.ts +4 -1
- package/dist/cli/create.js +42 -37
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1249 -0
- package/dist/cli/migrate-operation-shape.d.ts +44 -0
- package/dist/cli/migrate-operation-shape.js +113 -0
- package/dist/cli/migrate-provider-shape.d.ts +52 -0
- package/dist/cli/migrate-provider-shape.js +578 -0
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
- package/dist/cli/templates/provider/README.md.tpl +5 -5
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/dist/cli/templates/provider/provider.json.tpl +6 -0
- package/dist/config/loader.d.ts +179 -16
- package/dist/config/loader.js +435 -127
- package/dist/contract-serialization.js +4 -8
- package/dist/contract-types.d.ts +12 -5
- package/dist/contract.js +24 -10
- package/dist/declaration-validation.d.ts +33 -0
- package/dist/declaration-validation.js +266 -0
- package/dist/define.d.ts +66 -29
- package/dist/define.js +778 -69
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/error-observability.d.ts +7 -0
- package/dist/error-observability.js +61 -0
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +122 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +40 -0
- package/dist/fixture-sanitization.d.ts +28 -0
- package/dist/fixture-sanitization.js +227 -0
- package/dist/health-scenario.d.ts +1842 -0
- package/dist/health-scenario.js +624 -0
- package/dist/index.d.ts +21 -10
- package/dist/index.js +12 -7
- package/dist/lint.d.ts +23 -11
- package/dist/lint.js +572 -69
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +10 -2
- package/dist/provider.js +4 -1
- package/dist/runtime/auth-flow.d.ts +5 -1
- package/dist/runtime/auth-flow.js +6 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +492 -49
- package/dist/runtime/cache.d.ts +1 -0
- package/dist/runtime/cache.js +169 -15
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +13 -1
- package/dist/runtime/choice.js +490 -102
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +3 -2
- package/dist/runtime/executor.js +26 -25
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +515 -53
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +366 -8
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/proxy-errors.js +6 -2
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-telemetry.d.ts +80 -1
- package/dist/runtime/proxy-telemetry.js +154 -47
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +14 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
- package/dist/runtime/resolver-vendors/browser.js +377 -0
- package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
- package/dist/runtime/resolver-vendors/capsolver.js +531 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +408 -0
- package/dist/runtime/resolver-vendors/types.d.ts +94 -0
- package/dist/runtime/resolver-vendors/types.js +96 -0
- package/dist/runtime/resolver.d.ts +72 -0
- package/dist/runtime/resolver.js +771 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +277 -71
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +41 -5
- package/dist/runtime/stealth.js +997 -291
- package/dist/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace-config.d.ts +12 -0
- package/dist/runtime/trace-config.js +62 -0
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/error-observability.d.ts +1 -0
- package/dist/server/error-observability.js +1 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +2 -2
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test.d.ts +14 -3
- package/dist/server/self-test.js +129 -58
- package/dist/server/serve-implementation.d.ts +230 -0
- package/dist/server/serve-implementation.js +2265 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1131
- package/dist/server/trace-output.d.ts +6 -0
- package/dist/server/trace-output.js +79 -0
- package/dist/server/types.d.ts +30 -5
- package/dist/server/types.js +13 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +9 -4
- package/dist/stealth/profiles.js +160 -211
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +2 -1
- package/dist/testing/index.js +2 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +489 -21
- package/dist/trace-sanitization.d.ts +5 -0
- package/dist/trace-sanitization.js +45 -0
- package/dist/types.d.ts +673 -131
- package/dist/types.js +1 -0
- package/package.json +45 -5
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +118 -0
- package/src/ceremonies/index.ts +189 -46
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +33 -0
- package/src/cli/create.ts +50 -37
- package/src/cli/migrate-operation-declaration.ts +1751 -0
- package/src/cli/migrate-operation-shape.ts +184 -0
- package/src/cli/migrate-provider-shape.ts +772 -0
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/Dockerfile.tpl +1 -1
- package/src/cli/templates/provider/README.md.tpl +5 -5
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
- package/src/cli/templates/provider/provider.json.tpl +6 -0
- package/src/config/loader.ts +690 -163
- package/src/contract-serialization.ts +5 -7
- package/src/contract-types.ts +12 -5
- package/src/contract.ts +24 -10
- package/src/declaration-validation.ts +330 -0
- package/src/define.ts +1048 -131
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/error-observability.ts +64 -0
- package/src/error-resolution.ts +127 -0
- package/src/errors.ts +68 -0
- package/src/fixture-sanitization.ts +264 -0
- package/src/health-scenario.ts +875 -0
- package/src/index.ts +220 -13
- package/src/lint.ts +698 -90
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +161 -3
- package/src/runtime/auth-flow.ts +12 -0
- package/src/runtime/browser.ts +661 -63
- package/src/runtime/cache.ts +189 -14
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +631 -120
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +43 -31
- package/src/runtime/http.ts +641 -61
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/instrumentation.ts +520 -15
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/proxy-errors.ts +12 -4
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-telemetry.ts +244 -75
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +23 -0
- package/src/runtime/resolver-shared.ts +33 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +533 -0
- package/src/runtime/resolver-vendors/capsolver.ts +707 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
- package/src/runtime/resolver-vendors/types.ts +212 -0
- package/src/runtime/resolver.ts +1157 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +394 -77
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +1280 -336
- package/src/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace-config.ts +78 -0
- package/src/runtime/trace.ts +57 -17
- package/src/serve.ts +6 -1
- package/src/server/error-observability.ts +1 -0
- package/src/server/index.ts +34 -2
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test.ts +191 -68
- package/src/server/serve-implementation.ts +3378 -0
- package/src/server/serve.ts +1 -1632
- package/src/server/trace-output.ts +129 -0
- package/src/server/types.ts +13 -1
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +202 -231
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +8 -1
- package/src/testing/index.ts +10 -1
- package/src/testing/run.ts +658 -15
- package/src/trace-sanitization.ts +63 -0
- package/src/types.ts +788 -174
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
import type TS from "typescript";
|
|
2
|
+
|
|
3
|
+
const ts: typeof import("typescript") = await loadTypeScript();
|
|
4
|
+
|
|
5
|
+
async function loadTypeScript(): Promise<typeof import("typescript")> {
|
|
6
|
+
try {
|
|
7
|
+
return await import("typescript");
|
|
8
|
+
} catch {
|
|
9
|
+
console.error(
|
|
10
|
+
"apifuse migrate-shape requires typescript; install it in the workspace running the CLI (bun add -d typescript)",
|
|
11
|
+
);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Provider authoring shape migration.
|
|
18
|
+
*
|
|
19
|
+
* `defineProvider` changed in 2.2.0-beta.37 from returning a finished provider
|
|
20
|
+
* to returning a builder, splitting authoring into two phases:
|
|
21
|
+
*
|
|
22
|
+
* const buildProvider = defineProvider(<declaration>);
|
|
23
|
+
* export type ProviderContext = ProviderContextOf<typeof buildProvider>;
|
|
24
|
+
* export default buildProvider({ operations });
|
|
25
|
+
*
|
|
26
|
+
* Source written against the single-phase shape still type-checks against an
|
|
27
|
+
* older pin but default-exports a builder function once the pin moves, so
|
|
28
|
+
* `loadProviderDefinition` rejects it. Bumping the pin without migrating the
|
|
29
|
+
* source therefore breaks the module. This transform performs the source half
|
|
30
|
+
* so the SDK bump fan-out can ship both in one commit.
|
|
31
|
+
*
|
|
32
|
+
* The transform is deliberately conservative: it rewrites only the shapes it
|
|
33
|
+
* can fully account for and reports `skipped` with a reason for anything else,
|
|
34
|
+
* rather than emitting a partial migration a reviewer would have to audit.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** Every source shape this transform recognizes. */
|
|
38
|
+
export type ProviderShapeKind =
|
|
39
|
+
/** `export default defineProvider({ ..., operations })` */
|
|
40
|
+
| "single-phase-default-export"
|
|
41
|
+
/** `const p = defineProvider({ ..., operations }); export default p;` */
|
|
42
|
+
| "single-phase-variable-export"
|
|
43
|
+
/** `const p = defineProvider({ ..., operations }); export default { ...p, deployment };` */
|
|
44
|
+
| "single-phase-variable-spread-export"
|
|
45
|
+
/** Already `const b = defineProvider(...); export default b({ operations })` */
|
|
46
|
+
| "two-phase";
|
|
47
|
+
|
|
48
|
+
export type ProviderShapeMigration =
|
|
49
|
+
| {
|
|
50
|
+
readonly status: "migrated";
|
|
51
|
+
readonly kind: ProviderShapeKind;
|
|
52
|
+
readonly code: string;
|
|
53
|
+
/** Source text the operations map was supplied as, for reporting. */
|
|
54
|
+
readonly operationsExpression: string;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
readonly status: "unchanged";
|
|
58
|
+
readonly kind: "two-phase";
|
|
59
|
+
readonly code: string;
|
|
60
|
+
}
|
|
61
|
+
| {
|
|
62
|
+
readonly status: "skipped";
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const DECLARATION_BUILDER_NAME = "buildProvider";
|
|
67
|
+
const PROVIDER_CONTEXT_TYPE_NAME = "ProviderContext";
|
|
68
|
+
const PROVIDER_CONTEXT_OF_TYPE_NAME = "ProviderContextOf";
|
|
69
|
+
const PROVIDER_SDK_PROVIDER_SUBPATH = "@apifuse/provider-sdk/provider";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Migrate one provider `index.ts` to the two-phase authoring shape.
|
|
73
|
+
*
|
|
74
|
+
* Returns the rewritten source on success. Callers MUST treat `skipped` as a
|
|
75
|
+
* hard stop for that provider — a skipped provider needs a human, and pairing
|
|
76
|
+
* a pin bump with a skipped migration produces an unloadable module.
|
|
77
|
+
*/
|
|
78
|
+
export function migrateProviderShape(
|
|
79
|
+
sourceText: string,
|
|
80
|
+
fileName = "index.ts",
|
|
81
|
+
): ProviderShapeMigration {
|
|
82
|
+
const source = ts.createSourceFile(
|
|
83
|
+
fileName,
|
|
84
|
+
sourceText,
|
|
85
|
+
ts.ScriptTarget.Latest,
|
|
86
|
+
/* setParentNodes */ true,
|
|
87
|
+
ts.ScriptKind.TS,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const syntaxError = firstSyntaxError(source);
|
|
91
|
+
if (syntaxError !== undefined) {
|
|
92
|
+
return { status: "skipped", reason: syntaxError };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const calls = collectDefineProviderCalls(source);
|
|
96
|
+
if (calls.length === 0) {
|
|
97
|
+
return {
|
|
98
|
+
status: "skipped",
|
|
99
|
+
reason:
|
|
100
|
+
"No defineProvider(...) call found; this file does not declare a provider.",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (calls.length > 1) {
|
|
104
|
+
return {
|
|
105
|
+
status: "skipped",
|
|
106
|
+
reason: `Found ${calls.length} defineProvider(...) calls; the transform rewrites exactly one declaration.`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const call = calls[0];
|
|
111
|
+
if (call === undefined) {
|
|
112
|
+
return { status: "skipped", reason: "Internal: defineProvider call vanished." };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const exportAssignment = findDefaultExport(source);
|
|
116
|
+
if (exportAssignment === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
status: "skipped",
|
|
119
|
+
reason:
|
|
120
|
+
"No `export default` found; the provider module must default-export its provider.",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const declaration = call.arguments[0];
|
|
125
|
+
if (declaration === undefined || !ts.isObjectLiteralExpression(declaration)) {
|
|
126
|
+
return {
|
|
127
|
+
status: "skipped",
|
|
128
|
+
reason:
|
|
129
|
+
"defineProvider(...) is called with a non-literal argument, so the declaration's operations key cannot be located.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const operationsProperty = findOperationsProperty(declaration);
|
|
134
|
+
|
|
135
|
+
// Already migrated: the declaration carries no operations key and the
|
|
136
|
+
// default export path calls a builder variable rather than defineProvider.
|
|
137
|
+
if (
|
|
138
|
+
operationsProperty === undefined &&
|
|
139
|
+
isAlreadyTwoPhase(source, exportAssignment, call)
|
|
140
|
+
) {
|
|
141
|
+
return { status: "unchanged", kind: "two-phase", code: sourceText };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (operationsProperty === undefined) {
|
|
145
|
+
return {
|
|
146
|
+
status: "skipped",
|
|
147
|
+
reason:
|
|
148
|
+
"The declaration has no `operations` key and the default export does not call a declaration builder, so the intended shape is ambiguous.",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const operationsText = operationsPropertyValueText(operationsProperty, source);
|
|
153
|
+
if (operationsText === undefined) {
|
|
154
|
+
return {
|
|
155
|
+
status: "skipped",
|
|
156
|
+
reason:
|
|
157
|
+
"The `operations` property uses a form the transform cannot relocate (getter, setter, method, spread, or computed name).",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const shape = classifyShape(source, call, exportAssignment);
|
|
162
|
+
if (shape.status === "skipped") {
|
|
163
|
+
return shape;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const edits: TextEdit[] = [];
|
|
167
|
+
const builderName = pickBuilderName(source);
|
|
168
|
+
|
|
169
|
+
// 1. Remove `operations` from the declaration literal.
|
|
170
|
+
edits.push(...removeOperationsProperty(operationsProperty, declaration, source));
|
|
171
|
+
|
|
172
|
+
// 2. Bind the declaration to `const buildProvider = defineProvider({...})`.
|
|
173
|
+
edits.push(...introduceBuilder(source, call, shape, builderName));
|
|
174
|
+
|
|
175
|
+
// 3. Route the default export through `buildProvider({ operations })`.
|
|
176
|
+
edits.push(
|
|
177
|
+
...rewriteDefaultExport(source, exportAssignment, shape, builderName, operationsText),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// 4. `export type ProviderContext = ProviderContextOf<typeof buildProvider>;`
|
|
181
|
+
// plus the type-only import, when neither is already present.
|
|
182
|
+
edits.push(...ensureProviderContextType(source, call, shape, builderName));
|
|
183
|
+
|
|
184
|
+
const code = applyEdits(sourceText, edits);
|
|
185
|
+
|
|
186
|
+
// Re-parse the output: a transform that emits unparseable source is worse
|
|
187
|
+
// than one that skips, because the pin bump would ship alongside it.
|
|
188
|
+
const verified = ts.createSourceFile(
|
|
189
|
+
fileName,
|
|
190
|
+
code,
|
|
191
|
+
ts.ScriptTarget.Latest,
|
|
192
|
+
true,
|
|
193
|
+
ts.ScriptKind.TS,
|
|
194
|
+
);
|
|
195
|
+
const outputError = firstSyntaxError(verified);
|
|
196
|
+
if (outputError !== undefined) {
|
|
197
|
+
return {
|
|
198
|
+
status: "skipped",
|
|
199
|
+
reason: `Transform produced source that does not parse (${outputError}); refusing to emit a partial migration.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
status: "migrated",
|
|
205
|
+
kind: shape.kind,
|
|
206
|
+
code,
|
|
207
|
+
operationsExpression: operationsText,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
type TextEdit = {
|
|
212
|
+
readonly start: number;
|
|
213
|
+
readonly end: number;
|
|
214
|
+
readonly text: string;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
type ShapeClassification =
|
|
218
|
+
| {
|
|
219
|
+
readonly status: "ok";
|
|
220
|
+
readonly kind: Exclude<ProviderShapeKind, "two-phase">;
|
|
221
|
+
/** Variable the declaration is currently bound to, when it is bound. */
|
|
222
|
+
readonly variableName?: string;
|
|
223
|
+
readonly variableStatement?: TS.VariableStatement;
|
|
224
|
+
/** Extra properties on a `{ ...provider, deployment }` default export. */
|
|
225
|
+
readonly spreadExportExtras?: string;
|
|
226
|
+
}
|
|
227
|
+
| { readonly status: "skipped"; readonly reason: string };
|
|
228
|
+
|
|
229
|
+
function classifyShape(
|
|
230
|
+
source: TS.SourceFile,
|
|
231
|
+
call: TS.CallExpression,
|
|
232
|
+
exportAssignment: TS.ExportAssignment,
|
|
233
|
+
): ShapeClassification {
|
|
234
|
+
const variableStatement = enclosingVariableStatement(call);
|
|
235
|
+
|
|
236
|
+
if (variableStatement === undefined) {
|
|
237
|
+
// `export default defineProvider({...})`
|
|
238
|
+
if (exportAssignment.expression === call) {
|
|
239
|
+
return { status: "ok", kind: "single-phase-default-export" };
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
status: "skipped",
|
|
243
|
+
reason:
|
|
244
|
+
"defineProvider(...) is neither bound to a variable nor the default-export expression, so the transform cannot place the builder.",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const declarations = variableStatement.declarationList.declarations;
|
|
249
|
+
if (declarations.length !== 1) {
|
|
250
|
+
return {
|
|
251
|
+
status: "skipped",
|
|
252
|
+
reason:
|
|
253
|
+
"The defineProvider(...) result is declared alongside other bindings in one statement; split the declaration first.",
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const declaration = declarations[0];
|
|
257
|
+
if (declaration === undefined || !ts.isIdentifier(declaration.name)) {
|
|
258
|
+
return {
|
|
259
|
+
status: "skipped",
|
|
260
|
+
reason: "The defineProvider(...) result is bound to a destructuring pattern.",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (declaration.initializer !== call) {
|
|
264
|
+
return {
|
|
265
|
+
status: "skipped",
|
|
266
|
+
reason:
|
|
267
|
+
"defineProvider(...) is nested inside a larger initializer expression the transform cannot rewrite.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const variableName = declaration.name.text;
|
|
271
|
+
|
|
272
|
+
const exported = exportAssignment.expression;
|
|
273
|
+
|
|
274
|
+
if (ts.isIdentifier(exported) && exported.text === variableName) {
|
|
275
|
+
return {
|
|
276
|
+
status: "ok",
|
|
277
|
+
kind: "single-phase-variable-export",
|
|
278
|
+
variableName,
|
|
279
|
+
variableStatement,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (ts.isObjectLiteralExpression(exported)) {
|
|
284
|
+
const spreads = exported.properties.filter(ts.isSpreadAssignment);
|
|
285
|
+
const spreadsProvider = spreads.some(
|
|
286
|
+
(property) =>
|
|
287
|
+
ts.isIdentifier(property.expression) &&
|
|
288
|
+
property.expression.text === variableName,
|
|
289
|
+
);
|
|
290
|
+
if (!spreadsProvider) {
|
|
291
|
+
return {
|
|
292
|
+
status: "skipped",
|
|
293
|
+
reason: `The default export is an object literal that does not spread \`${variableName}\`, so the provider value it exports is unclear.`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (spreads.length > 1) {
|
|
297
|
+
return {
|
|
298
|
+
status: "skipped",
|
|
299
|
+
reason:
|
|
300
|
+
"The default export spreads more than one value; the transform cannot tell which carries the provider.",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const extras = exported.properties.filter(
|
|
304
|
+
(property) => !ts.isSpreadAssignment(property),
|
|
305
|
+
);
|
|
306
|
+
return {
|
|
307
|
+
status: "ok",
|
|
308
|
+
kind: "single-phase-variable-spread-export",
|
|
309
|
+
variableName,
|
|
310
|
+
variableStatement,
|
|
311
|
+
spreadExportExtras: extras
|
|
312
|
+
.map((property) => property.getText(source))
|
|
313
|
+
.join(",\n "),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
status: "skipped",
|
|
319
|
+
reason: `The default export is neither \`${variableName}\` nor an object literal spreading it.`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function collectDefineProviderCalls(source: TS.SourceFile): TS.CallExpression[] {
|
|
324
|
+
const calls: TS.CallExpression[] = [];
|
|
325
|
+
const visit = (node: TS.Node): void => {
|
|
326
|
+
if (
|
|
327
|
+
ts.isCallExpression(node) &&
|
|
328
|
+
ts.isIdentifier(node.expression) &&
|
|
329
|
+
node.expression.text === "defineProvider"
|
|
330
|
+
) {
|
|
331
|
+
calls.push(node);
|
|
332
|
+
}
|
|
333
|
+
ts.forEachChild(node, visit);
|
|
334
|
+
};
|
|
335
|
+
visit(source);
|
|
336
|
+
return calls;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function findDefaultExport(source: TS.SourceFile): TS.ExportAssignment | undefined {
|
|
340
|
+
for (const statement of source.statements) {
|
|
341
|
+
if (ts.isExportAssignment(statement) && statement.isExportEquals !== true) {
|
|
342
|
+
return statement;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function defaultExportCallsBuilder(
|
|
349
|
+
exportAssignment: TS.ExportAssignment,
|
|
350
|
+
declarationCall: TS.CallExpression,
|
|
351
|
+
): boolean {
|
|
352
|
+
const expression = exportAssignment.expression;
|
|
353
|
+
if (!ts.isCallExpression(expression)) return false;
|
|
354
|
+
if (expression === declarationCall) return false;
|
|
355
|
+
return ts.isIdentifier(expression.expression);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* True when the module is already in the two-phase shape: the default export
|
|
360
|
+
* resolves to a builder-call result. Covers the three migrated layouts —
|
|
361
|
+
* `export default buildProvider({...})`, an intermediate
|
|
362
|
+
* `const provider = buildProvider({...}); export default provider;`, and the
|
|
363
|
+
* spread export `export default { ...provider, deployment }` over such a
|
|
364
|
+
* binding. Without the latter two, re-running the transform on its own
|
|
365
|
+
* spread-shape output reports "ambiguous" instead of "unchanged", which
|
|
366
|
+
* breaks idempotency for repeated fan-out runs.
|
|
367
|
+
*/
|
|
368
|
+
function isAlreadyTwoPhase(
|
|
369
|
+
source: TS.SourceFile,
|
|
370
|
+
exportAssignment: TS.ExportAssignment,
|
|
371
|
+
declarationCall: TS.CallExpression,
|
|
372
|
+
): boolean {
|
|
373
|
+
if (defaultExportCallsBuilder(exportAssignment, declarationCall)) return true;
|
|
374
|
+
|
|
375
|
+
const exported = exportAssignment.expression;
|
|
376
|
+
const candidateNames: string[] = [];
|
|
377
|
+
if (ts.isIdentifier(exported)) {
|
|
378
|
+
candidateNames.push(exported.text);
|
|
379
|
+
} else if (ts.isObjectLiteralExpression(exported)) {
|
|
380
|
+
for (const property of exported.properties) {
|
|
381
|
+
if (ts.isSpreadAssignment(property) && ts.isIdentifier(property.expression)) {
|
|
382
|
+
candidateNames.push(property.expression.text);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (candidateNames.length === 0) return false;
|
|
387
|
+
|
|
388
|
+
// Does any spread/exported identifier bind a call to an identifier other
|
|
389
|
+
// than defineProvider — i.e. a builder call?
|
|
390
|
+
let found = false;
|
|
391
|
+
const visit = (node: TS.Node): void => {
|
|
392
|
+
if (found) return;
|
|
393
|
+
if (
|
|
394
|
+
ts.isVariableDeclaration(node) &&
|
|
395
|
+
ts.isIdentifier(node.name) &&
|
|
396
|
+
candidateNames.includes(node.name.text) &&
|
|
397
|
+
node.initializer !== undefined &&
|
|
398
|
+
ts.isCallExpression(node.initializer) &&
|
|
399
|
+
node.initializer !== declarationCall &&
|
|
400
|
+
ts.isIdentifier(node.initializer.expression) &&
|
|
401
|
+
node.initializer.expression.text !== "defineProvider"
|
|
402
|
+
) {
|
|
403
|
+
found = true;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
ts.forEachChild(node, visit);
|
|
407
|
+
};
|
|
408
|
+
visit(source);
|
|
409
|
+
return found;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function findOperationsProperty(
|
|
413
|
+
declaration: TS.ObjectLiteralExpression,
|
|
414
|
+
): TS.ObjectLiteralElementLike | undefined {
|
|
415
|
+
for (const property of declaration.properties) {
|
|
416
|
+
if (ts.isSpreadAssignment(property)) continue;
|
|
417
|
+
const name = property.name;
|
|
418
|
+
if (name === undefined) continue;
|
|
419
|
+
if (
|
|
420
|
+
(ts.isIdentifier(name) || ts.isStringLiteral(name)) &&
|
|
421
|
+
name.text === "operations"
|
|
422
|
+
) {
|
|
423
|
+
return property;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Source text of the value to hand the builder. Shorthand becomes the bare
|
|
431
|
+
* identifier so `{ operations }` stays idiomatic; a property assignment keeps
|
|
432
|
+
* its initializer verbatim, including a multi-line inline map.
|
|
433
|
+
*/
|
|
434
|
+
function operationsPropertyValueText(
|
|
435
|
+
property: TS.ObjectLiteralElementLike,
|
|
436
|
+
source: TS.SourceFile,
|
|
437
|
+
): string | undefined {
|
|
438
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
439
|
+
return property.name.text;
|
|
440
|
+
}
|
|
441
|
+
if (ts.isPropertyAssignment(property)) {
|
|
442
|
+
return property.initializer.getText(source);
|
|
443
|
+
}
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function removeOperationsProperty(
|
|
448
|
+
property: TS.ObjectLiteralElementLike,
|
|
449
|
+
declaration: TS.ObjectLiteralExpression,
|
|
450
|
+
source: TS.SourceFile,
|
|
451
|
+
): TextEdit[] {
|
|
452
|
+
const properties = declaration.properties;
|
|
453
|
+
const index = properties.indexOf(property);
|
|
454
|
+
const start = property.getFullStart();
|
|
455
|
+
let end = property.getEnd();
|
|
456
|
+
|
|
457
|
+
// Absorb the trailing comma so the remaining literal stays well-formed,
|
|
458
|
+
// whether the key sat mid-list or last.
|
|
459
|
+
const text = source.getFullText();
|
|
460
|
+
let cursor = end;
|
|
461
|
+
while (cursor < text.length && /\s/.test(text.charAt(cursor))) cursor += 1;
|
|
462
|
+
if (text.charAt(cursor) === ",") {
|
|
463
|
+
end = cursor + 1;
|
|
464
|
+
} else if (index > 0) {
|
|
465
|
+
// Last property with no trailing comma: drop the preceding one instead.
|
|
466
|
+
const previous = properties[index - 1];
|
|
467
|
+
if (previous !== undefined) {
|
|
468
|
+
let back = previous.getEnd();
|
|
469
|
+
while (back < text.length && /\s/.test(text.charAt(back))) back += 1;
|
|
470
|
+
if (text.charAt(back) === ",") {
|
|
471
|
+
return [{ start: back, end, text: "" }];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return [{ start, end, text: "" }];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function introduceBuilder(
|
|
480
|
+
source: TS.SourceFile,
|
|
481
|
+
call: TS.CallExpression,
|
|
482
|
+
shape: Extract<ShapeClassification, { status: "ok" }>,
|
|
483
|
+
builderName: string,
|
|
484
|
+
): TextEdit[] {
|
|
485
|
+
if (shape.kind === "single-phase-default-export") {
|
|
486
|
+
// `export default defineProvider({...})` becomes a standalone builder
|
|
487
|
+
// declaration; the default export is re-appended separately.
|
|
488
|
+
const exportStatement = call.parent;
|
|
489
|
+
if (!ts.isExportAssignment(exportStatement)) return [];
|
|
490
|
+
return [
|
|
491
|
+
{
|
|
492
|
+
start: exportStatement.getStart(source),
|
|
493
|
+
end: call.getStart(source),
|
|
494
|
+
text: `const ${builderName} = `,
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const statement = shape.variableStatement;
|
|
500
|
+
if (statement === undefined || shape.variableName === undefined) return [];
|
|
501
|
+
|
|
502
|
+
// Rename the existing binding to the builder name. The old name is
|
|
503
|
+
// re-introduced for the built provider only in the spread-export shape,
|
|
504
|
+
// which needs an intermediate value to spread.
|
|
505
|
+
const declaration = statement.declarationList.declarations[0];
|
|
506
|
+
if (declaration === undefined || !ts.isIdentifier(declaration.name)) return [];
|
|
507
|
+
return [
|
|
508
|
+
{
|
|
509
|
+
start: declaration.name.getStart(source),
|
|
510
|
+
end: declaration.name.getEnd(),
|
|
511
|
+
text: builderName,
|
|
512
|
+
},
|
|
513
|
+
];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function rewriteDefaultExport(
|
|
517
|
+
source: TS.SourceFile,
|
|
518
|
+
exportAssignment: TS.ExportAssignment,
|
|
519
|
+
shape: Extract<ShapeClassification, { status: "ok" }>,
|
|
520
|
+
builderName: string,
|
|
521
|
+
operationsText: string,
|
|
522
|
+
): TextEdit[] {
|
|
523
|
+
const operationsArgument =
|
|
524
|
+
operationsText === "operations"
|
|
525
|
+
? "{ operations }"
|
|
526
|
+
: `{ operations: ${operationsText} }`;
|
|
527
|
+
const call = `${builderName}(${operationsArgument})`;
|
|
528
|
+
|
|
529
|
+
if (shape.kind === "single-phase-default-export") {
|
|
530
|
+
// The builder declaration replaced the `export default` prefix; the
|
|
531
|
+
// export itself is appended after what is now the builder statement.
|
|
532
|
+
return [
|
|
533
|
+
{
|
|
534
|
+
start: exportAssignment.getEnd(),
|
|
535
|
+
end: exportAssignment.getEnd(),
|
|
536
|
+
text: `\n\nexport default ${call};`,
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (shape.kind === "single-phase-variable-export") {
|
|
542
|
+
return [
|
|
543
|
+
{
|
|
544
|
+
start: exportAssignment.expression.getStart(source),
|
|
545
|
+
end: exportAssignment.expression.getEnd(),
|
|
546
|
+
text: call,
|
|
547
|
+
},
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Spread export: re-introduce the provider binding so the extra properties
|
|
552
|
+
// (currently `deployment`) still spread over a built provider.
|
|
553
|
+
const providerName = shape.variableName ?? "provider";
|
|
554
|
+
const extras = shape.spreadExportExtras ?? "";
|
|
555
|
+
const rebuilt =
|
|
556
|
+
extras.length > 0
|
|
557
|
+
? `{\n ...${providerName},\n ${extras},\n}`
|
|
558
|
+
: `{ ...${providerName} }`;
|
|
559
|
+
return [
|
|
560
|
+
{
|
|
561
|
+
start: exportAssignment.getStart(source),
|
|
562
|
+
end: exportAssignment.getStart(source),
|
|
563
|
+
text: `const ${providerName} = ${call};\n\n`,
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
start: exportAssignment.expression.getStart(source),
|
|
567
|
+
end: exportAssignment.expression.getEnd(),
|
|
568
|
+
text: rebuilt,
|
|
569
|
+
},
|
|
570
|
+
];
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Add `export type ProviderContext = ProviderContextOf<typeof buildProvider>`
|
|
575
|
+
* and the type-only import when absent. Operation handlers reference this
|
|
576
|
+
* type, so a migration that omits it leaves the provider without the context
|
|
577
|
+
* type the two-phase shape exists to provide.
|
|
578
|
+
*/
|
|
579
|
+
function ensureProviderContextType(
|
|
580
|
+
source: TS.SourceFile,
|
|
581
|
+
call: TS.CallExpression,
|
|
582
|
+
shape: Extract<ShapeClassification, { status: "ok" }>,
|
|
583
|
+
builderName: string,
|
|
584
|
+
): TextEdit[] {
|
|
585
|
+
const text = source.getFullText();
|
|
586
|
+
const edits: TextEdit[] = [];
|
|
587
|
+
|
|
588
|
+
const hasContextTypeAlias = source.statements.some(
|
|
589
|
+
(statement) =>
|
|
590
|
+
ts.isTypeAliasDeclaration(statement) &&
|
|
591
|
+
statement.name.text === PROVIDER_CONTEXT_TYPE_NAME,
|
|
592
|
+
);
|
|
593
|
+
// A named import of `ProviderContext` (the deprecated SDK-root context
|
|
594
|
+
// type, imported by legacy sources) occupies the same name: adding the
|
|
595
|
+
// alias alongside it is TS2440. The import must yield to the derived
|
|
596
|
+
// alias — the whole point of the two-phase shape — so drop it and let the
|
|
597
|
+
// alias own the name.
|
|
598
|
+
const conflictingImportSpecifier = findNamedImportSpecifier(
|
|
599
|
+
source,
|
|
600
|
+
PROVIDER_CONTEXT_TYPE_NAME,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
if (!hasContextTypeAlias) {
|
|
604
|
+
// After the builder statement — which is the variable statement when the
|
|
605
|
+
// declaration was bound, or the rewritten export statement otherwise.
|
|
606
|
+
const anchor =
|
|
607
|
+
shape.variableStatement ??
|
|
608
|
+
(ts.isExportAssignment(call.parent) ? call.parent : undefined);
|
|
609
|
+
if (anchor !== undefined) {
|
|
610
|
+
const insertAt = anchor.getEnd();
|
|
611
|
+
edits.push({
|
|
612
|
+
start: insertAt,
|
|
613
|
+
end: insertAt,
|
|
614
|
+
text: `\n\nexport type ${PROVIDER_CONTEXT_TYPE_NAME} = ${PROVIDER_CONTEXT_OF_TYPE_NAME}<typeof ${builderName}>;`,
|
|
615
|
+
});
|
|
616
|
+
if (conflictingImportSpecifier !== undefined) {
|
|
617
|
+
edits.push(removeImportSpecifier(source, conflictingImportSpecifier));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (text.includes(PROVIDER_CONTEXT_OF_TYPE_NAME)) {
|
|
623
|
+
return edits;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const providerImport = findProviderSdkImport(source);
|
|
627
|
+
if (providerImport === undefined) {
|
|
628
|
+
// No named import from the provider subpath to extend; adding a new
|
|
629
|
+
// import line without knowing the module's style is riskier than
|
|
630
|
+
// leaving the type import to `bun run check` feedback.
|
|
631
|
+
return edits;
|
|
632
|
+
}
|
|
633
|
+
const named = providerImport.importClause?.namedBindings;
|
|
634
|
+
if (named === undefined || !ts.isNamedImports(named)) {
|
|
635
|
+
return edits;
|
|
636
|
+
}
|
|
637
|
+
const last = named.elements[named.elements.length - 1];
|
|
638
|
+
if (last === undefined) {
|
|
639
|
+
return edits;
|
|
640
|
+
}
|
|
641
|
+
edits.push({
|
|
642
|
+
start: last.getEnd(),
|
|
643
|
+
end: last.getEnd(),
|
|
644
|
+
text: `, type ${PROVIDER_CONTEXT_OF_TYPE_NAME}`,
|
|
645
|
+
});
|
|
646
|
+
return edits;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function findProviderSdkImport(
|
|
650
|
+
source: TS.SourceFile,
|
|
651
|
+
): TS.ImportDeclaration | undefined {
|
|
652
|
+
for (const statement of source.statements) {
|
|
653
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
654
|
+
const moduleSpecifier = statement.moduleSpecifier;
|
|
655
|
+
if (!ts.isStringLiteral(moduleSpecifier)) continue;
|
|
656
|
+
if (moduleSpecifier.text !== PROVIDER_SDK_PROVIDER_SUBPATH) continue;
|
|
657
|
+
const named = statement.importClause?.namedBindings;
|
|
658
|
+
if (named !== undefined && ts.isNamedImports(named)) {
|
|
659
|
+
const importsDefineProvider = named.elements.some(
|
|
660
|
+
(element) => element.name.text === "defineProvider",
|
|
661
|
+
);
|
|
662
|
+
if (importsDefineProvider) return statement;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Named import specifier binding `localName` in any import declaration. */
|
|
669
|
+
function findNamedImportSpecifier(
|
|
670
|
+
source: TS.SourceFile,
|
|
671
|
+
localName: string,
|
|
672
|
+
): TS.ImportSpecifier | undefined {
|
|
673
|
+
for (const statement of source.statements) {
|
|
674
|
+
if (!ts.isImportDeclaration(statement)) continue;
|
|
675
|
+
const named = statement.importClause?.namedBindings;
|
|
676
|
+
if (named === undefined || !ts.isNamedImports(named)) continue;
|
|
677
|
+
for (const element of named.elements) {
|
|
678
|
+
if (element.name.text === localName) return element;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Edit removing one specifier from its named-import list, absorbing one
|
|
686
|
+
* neighboring comma so the list stays well-formed. Callers guarantee the
|
|
687
|
+
* list has at least one other specifier (legacy sources always import
|
|
688
|
+
* defineProvider alongside the context type).
|
|
689
|
+
*/
|
|
690
|
+
function removeImportSpecifier(
|
|
691
|
+
source: TS.SourceFile,
|
|
692
|
+
specifier: TS.ImportSpecifier,
|
|
693
|
+
): TextEdit {
|
|
694
|
+
const list = specifier.parent;
|
|
695
|
+
const index = list.elements.indexOf(specifier);
|
|
696
|
+
const text = source.getFullText();
|
|
697
|
+
let start = specifier.getFullStart();
|
|
698
|
+
let end = specifier.getEnd();
|
|
699
|
+
let cursor = end;
|
|
700
|
+
while (cursor < text.length && /\s/.test(text.charAt(cursor))) cursor += 1;
|
|
701
|
+
if (text.charAt(cursor) === ",") {
|
|
702
|
+
end = cursor + 1;
|
|
703
|
+
} else if (index > 0) {
|
|
704
|
+
const previous = list.elements[index - 1];
|
|
705
|
+
if (previous !== undefined) {
|
|
706
|
+
let back = previous.getEnd();
|
|
707
|
+
while (back < text.length && /\s/.test(text.charAt(back))) back += 1;
|
|
708
|
+
if (text.charAt(back) === ",") start = back;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return { start, end, text: "" };
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* `buildProvider` unless the module already binds that name, in which case a
|
|
716
|
+
* numbered suffix keeps the transform from shadowing an existing binding.
|
|
717
|
+
*/
|
|
718
|
+
function pickBuilderName(source: TS.SourceFile): string {
|
|
719
|
+
const taken = new Set<string>();
|
|
720
|
+
const visit = (node: TS.Node): void => {
|
|
721
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
722
|
+
taken.add(node.name.text);
|
|
723
|
+
}
|
|
724
|
+
if (ts.isFunctionDeclaration(node) && node.name !== undefined) {
|
|
725
|
+
taken.add(node.name.text);
|
|
726
|
+
}
|
|
727
|
+
if (ts.isImportSpecifier(node)) {
|
|
728
|
+
taken.add(node.name.text);
|
|
729
|
+
}
|
|
730
|
+
ts.forEachChild(node, visit);
|
|
731
|
+
};
|
|
732
|
+
visit(source);
|
|
733
|
+
|
|
734
|
+
if (!taken.has(DECLARATION_BUILDER_NAME)) return DECLARATION_BUILDER_NAME;
|
|
735
|
+
for (let suffix = 2; suffix < 100; suffix += 1) {
|
|
736
|
+
const candidate = `${DECLARATION_BUILDER_NAME}${suffix}`;
|
|
737
|
+
if (!taken.has(candidate)) return candidate;
|
|
738
|
+
}
|
|
739
|
+
return `${DECLARATION_BUILDER_NAME}Migrated`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function enclosingVariableStatement(node: TS.Node): TS.VariableStatement | undefined {
|
|
743
|
+
let current: TS.Node | undefined = node.parent;
|
|
744
|
+
while (current !== undefined) {
|
|
745
|
+
if (ts.isVariableStatement(current)) return current;
|
|
746
|
+
if (ts.isSourceFile(current)) return undefined;
|
|
747
|
+
current = current.parent;
|
|
748
|
+
}
|
|
749
|
+
return undefined;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function firstSyntaxError(source: TS.SourceFile): string | undefined {
|
|
753
|
+
const diagnostics = (
|
|
754
|
+
source as TS.SourceFile & { parseDiagnostics?: TS.DiagnosticWithLocation[] }
|
|
755
|
+
).parseDiagnostics;
|
|
756
|
+
if (diagnostics === undefined || diagnostics.length === 0) return undefined;
|
|
757
|
+
const first = diagnostics[0];
|
|
758
|
+
if (first === undefined) return undefined;
|
|
759
|
+
const message = ts.flattenDiagnosticMessageText(first.messageText, " ");
|
|
760
|
+
const { line } = source.getLineAndCharacterOfPosition(first.start);
|
|
761
|
+
return `${message} (line ${line + 1})`;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** Apply edits back-to-front so earlier offsets stay valid. */
|
|
765
|
+
function applyEdits(text: string, edits: readonly TextEdit[]): string {
|
|
766
|
+
const ordered = [...edits].sort((a, b) => b.start - a.start || b.end - a.end);
|
|
767
|
+
let output = text;
|
|
768
|
+
for (const edit of ordered) {
|
|
769
|
+
output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
|
|
770
|
+
}
|
|
771
|
+
return output;
|
|
772
|
+
}
|