-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[wrangler] Extract remote bindings into a package #14720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
159c673
[remote-bindings] Add empty package
penalosa 0c64cf4
[remote-bindings] Extract remote binding implementation
penalosa ee6e6c3
snapshot copy of DevEnv
penalosa 7f297eb
remove obsolete inspector proxy
penalosa 4ca824d
config pass through
penalosa 1a24473
bundler pass through
penalosa b27a054
logger context
penalosa b16a696
remove no-op
penalosa 9ea9466
fully specified remote bindings input config
penalosa 9b75588
remove dead code
penalosa 11a062a
packages
penalosa 3f78aca
packages
penalosa ebdb131
packages
penalosa c9f84a2
base move
penalosa bcc6143
move more helpers
penalosa 39cbe42
move retryOnApiFailure
penalosa fb41427
move access
penalosa a3700e2
copy ProxyWorekr
penalosa 01b325e
make proxycontroller green with minimal helpers
penalosa 1e0c276
reduction in more things we don't cara bout
penalosa d2dcd18
make remote bindings package functional
penalosa 3806b52
[remote-bindings] Remove dead dev runtime code
penalosa 920555b
[remote-bindings] Narrow remote runtime internals
penalosa b845d3a
[remote-bindings] Restore default account selection
penalosa 464b4b6
chore: fix remote bindings lockfile
penalosa 448c24a
[remote-bindings] Collapse remote runtime controllers
penalosa 5bdef90
[remote-bindings] Embed workers for direct consumers
penalosa 4376590
[remote-bindings] Fix direct consumer integration
penalosa 710e34f
[remote-bindings] Fix Windows worker embedding build
penalosa fb1afc2
[wrangler] Update invalid account error snapshot
penalosa 3ae2834
[remote-bindings] Prevent stale preview bindings
penalosa fe26a97
Update packages/remote-bindings/src/startDevWorker/RemoteRuntimeContr…
penalosa 3f012b6
[remote-bindings] Address review feedback
penalosa 2b3ea8b
[remote-bindings] Preserve UserError type from async proxy session st…
penalosa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "wrangler": patch | ||
| --- | ||
|
|
||
| Fix remote binding previews for accounts without a workers.dev subdomain | ||
|
|
||
| Wrangler now automatically registers a workers.dev subdomain when one is required to start a remote binding preview. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| { | ||
| "name": "@cloudflare/remote-bindings", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/remote-bindings#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/cloudflare/workers-sdk/issues" | ||
| }, | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/cloudflare/workers-sdk.git", | ||
| "directory": "packages/remote-bindings" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "type": "module", | ||
| "sideEffects": false, | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.mts", | ||
| "import": "./dist/index.mjs" | ||
| } | ||
| }, | ||
| "scripts": { | ||
| "build": "tsdown", | ||
| "check:type": "tsc", | ||
| "dev": "tsdown --watch", | ||
| "test:ci": "vitest run --passWithNoTests", | ||
| "test:watch": "vitest" | ||
| }, | ||
| "dependencies": { | ||
| "@cloudflare/cli-shared-helpers": "workspace:*", | ||
| "@cloudflare/deploy-helpers": "workspace:*", | ||
| "@cloudflare/workers-auth": "workspace:*", | ||
| "@cloudflare/workers-utils": "workspace:*", | ||
| "chalk": "catalog:default", | ||
| "miniflare": "workspace:*", | ||
| "undici": "catalog:default", | ||
| "ws": "catalog:default" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/workers-tsconfig": "workspace:*", | ||
| "@cloudflare/workers-types": "catalog:default", | ||
| "@types/ws": "^8.5.13", | ||
| "capnweb": "catalog:default", | ||
| "esbuild": "catalog:default", | ||
| "tsdown": "0.16.3", | ||
| "typescript": "catalog:default", | ||
| "vitest": "catalog:default" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import path from "node:path"; | ||
| import { build } from "esbuild"; | ||
|
|
||
| const WORKER_PREFIX = "\0worker:"; | ||
| const templatesDir = path.resolve(import.meta.dirname, "../templates"); | ||
|
|
||
| export function embedWorkersPlugin() { | ||
| return { | ||
| name: "embed-workers", | ||
| resolveId(id: string) { | ||
| if (!id.startsWith("worker:")) { | ||
| return; | ||
| } | ||
| return `${WORKER_PREFIX}${id.slice("worker:".length)}`; | ||
| }, | ||
| async load(id: string) { | ||
| if (!id.startsWith(WORKER_PREFIX)) { | ||
| return; | ||
| } | ||
| const result = await build({ | ||
| entryPoints: [ | ||
| path.resolve(templatesDir, `${id.slice(WORKER_PREFIX.length)}.ts`), | ||
| ], | ||
| platform: "node", | ||
| conditions: ["workerd", "worker", "browser"], | ||
| format: "esm", | ||
| target: "esnext", | ||
| bundle: true, | ||
| write: false, | ||
| external: ["cloudflare:email", "cloudflare:workers"], | ||
| }); | ||
| const source = result.outputFiles[0]?.text; | ||
| if (source === undefined) { | ||
| throw new Error(`Failed to bundle Worker ${id}`); | ||
| } | ||
| return `export default ${JSON.stringify(source)};`; | ||
| }, | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| import assert from "node:assert"; | ||
| import { afterEach, beforeEach, describe, it, vi } from "vitest"; | ||
| import { createRemoteBindingsAuth, getRemoteBindingsAuthHook } from "./auth"; | ||
| import type { RemoteBindingsLogger } from "./logger"; | ||
| import type { CfAccount } from "@cloudflare/workers-utils"; | ||
|
|
||
| const mocks = vi.hoisted(() => ({ | ||
| cfAuth: { | ||
| source: "cf", | ||
| setProfile: vi.fn(), | ||
| requireAuth: vi.fn().mockResolvedValue("selected-account-id"), | ||
| requireApiToken: vi.fn().mockReturnValue({ apiToken: "test-token" }), | ||
| }, | ||
| wranglerAuth: { | ||
| source: "wrangler", | ||
| setProfile: vi.fn(), | ||
| requireAuth: vi.fn().mockResolvedValue("selected-account-id"), | ||
| requireApiToken: vi.fn().mockReturnValue({ apiToken: "test-token" }), | ||
| }, | ||
| createCfAuth: vi.fn(), | ||
| createWranglerAuth: vi.fn(), | ||
| cfProfileStore: { resolve: vi.fn().mockReturnValue({ name: "cf-profile" }) }, | ||
| wranglerProfileStore: { | ||
| resolve: vi.fn().mockReturnValue({ name: "wrangler-profile" }), | ||
| }, | ||
| createCfProfileStore: vi.fn(), | ||
| createWranglerProfileStore: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock("@cloudflare/workers-auth/cf", () => ({ | ||
| createCfAuth: mocks.createCfAuth.mockReturnValue(mocks.cfAuth), | ||
| createCfProfileStore: mocks.createCfProfileStore.mockReturnValue( | ||
| mocks.cfProfileStore | ||
| ), | ||
| })); | ||
|
|
||
| vi.mock("@cloudflare/workers-auth/wrangler", () => ({ | ||
| createWranglerAuth: mocks.createWranglerAuth.mockReturnValue( | ||
| mocks.wranglerAuth | ||
| ), | ||
| createWranglerProfileStore: mocks.createWranglerProfileStore.mockReturnValue( | ||
| mocks.wranglerProfileStore | ||
| ), | ||
| })); | ||
|
|
||
| const originalCfAuth = process.env.CLOUDFLARE_CF_AUTH; | ||
|
|
||
| function createTestLogger(): RemoteBindingsLogger { | ||
| return { | ||
| loggerLevel: "log", | ||
| debug: vi.fn(), | ||
| log: vi.fn(), | ||
| info: vi.fn(), | ||
| warn: vi.fn(), | ||
| error: vi.fn(), | ||
| console: vi.fn(), | ||
| }; | ||
| } | ||
|
|
||
| beforeEach(() => { | ||
| delete process.env.CLOUDFLARE_CF_AUTH; | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| vi.clearAllMocks(); | ||
| if (originalCfAuth === undefined) { | ||
| delete process.env.CLOUDFLARE_CF_AUTH; | ||
| } else { | ||
| process.env.CLOUDFLARE_CF_AUTH = originalCfAuth; | ||
| } | ||
| }); | ||
|
|
||
| describe("createRemoteBindingsAuth", () => { | ||
| it("uses Wrangler auth by default", ({ expect }) => { | ||
| const result = createRemoteBindingsAuth(createTestLogger()); | ||
|
|
||
| expect(result).toEqual({ auth: mocks.wranglerAuth, useCfAuth: false }); | ||
| expect(mocks.createWranglerAuth).toHaveBeenCalledOnce(); | ||
| expect(mocks.createCfAuth).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("uses CF auth when CLOUDFLARE_CF_AUTH is present", ({ expect }) => { | ||
| process.env.CLOUDFLARE_CF_AUTH = ""; | ||
|
|
||
| const result = createRemoteBindingsAuth(createTestLogger()); | ||
|
|
||
| expect(result).toEqual({ auth: mocks.cfAuth, useCfAuth: true }); | ||
| expect(mocks.createCfAuth).toHaveBeenCalledOnce(); | ||
| expect(mocks.createWranglerAuth).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("getRemoteBindingsAuthHook", () => { | ||
| it("uses provided auth without resolving a profile", ({ expect }) => { | ||
| const auth: CfAccount = { | ||
| accountId: "provided-account-id", | ||
| apiToken: { apiToken: "provided-token" }, | ||
| }; | ||
|
|
||
| const result = getRemoteBindingsAuthHook( | ||
| auth, | ||
| undefined, | ||
| undefined, | ||
| createTestLogger() | ||
| ); | ||
|
|
||
| expect(result).toBe(auth); | ||
| expect(mocks.createWranglerProfileStore).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("allows auth to select an account when none is configured", async ({ | ||
| expect, | ||
| }) => { | ||
| const hook = getRemoteBindingsAuthHook( | ||
| undefined, | ||
| undefined, | ||
| undefined, | ||
| createTestLogger() | ||
| ); | ||
| assert(typeof hook === "function"); | ||
|
|
||
| await expect(hook()).resolves.toEqual({ | ||
| accountId: "selected-account-id", | ||
| apiToken: { apiToken: "test-token" }, | ||
| }); | ||
| expect(mocks.wranglerAuth.requireAuth).toHaveBeenCalledWith({}); | ||
| }); | ||
|
|
||
| it("uses the configured account when provided", async ({ expect }) => { | ||
| const hook = getRemoteBindingsAuthHook( | ||
| undefined, | ||
| "configured-account-id", | ||
| undefined, | ||
| createTestLogger() | ||
| ); | ||
| assert(typeof hook === "function"); | ||
|
|
||
| await hook(); | ||
| expect(mocks.wranglerAuth.requireAuth).toHaveBeenCalledWith({ | ||
| account_id: "configured-account-id", | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import { inputPrompt } from "@cloudflare/cli-shared-helpers/interactive"; | ||
| import { | ||
| createCfAuth, | ||
| createCfProfileStore, | ||
| } from "@cloudflare/workers-auth/cf"; | ||
| import { | ||
| createWranglerAuth, | ||
| createWranglerProfileStore, | ||
| } from "@cloudflare/workers-auth/wrangler"; | ||
| import { isNonInteractiveOrCI, UserError } from "@cloudflare/workers-utils"; | ||
| import { version as packageVersion } from "../package.json"; | ||
| import type { RemoteBindingsLogger } from "./logger"; | ||
| import type { AsyncHook, CfAccount, Config } from "@cloudflare/workers-utils"; | ||
|
|
||
| class NoDefaultValueProvided extends UserError { | ||
| constructor() { | ||
| super("This command cannot be run in a non-interactive context", { | ||
| telemetryMessage: "remote bindings prompt default missing", | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| export function createRemoteBindingsAuth(logger: RemoteBindingsLogger) { | ||
| const context = { | ||
| logger, | ||
| userAgent: `remote-bindings/${packageVersion}`, | ||
| async prompt(question: string) { | ||
| if (isNonInteractiveOrCI()) { | ||
| throw new NoDefaultValueProvided(); | ||
| } | ||
| return inputPrompt<string>({ | ||
| type: "text", | ||
| question, | ||
| label: "Answer", | ||
| throwOnError: true, | ||
| }); | ||
| }, | ||
| async select( | ||
| question: string, | ||
| options: { choices: { title: string; value: string }[] } | ||
| ) { | ||
| if (isNonInteractiveOrCI()) { | ||
| throw new NoDefaultValueProvided(); | ||
| } | ||
| return inputPrompt<string>({ | ||
| type: "select", | ||
| question, | ||
| label: "Account", | ||
| options: options.choices.map((choice) => ({ | ||
| label: choice.title, | ||
| value: choice.value, | ||
| })), | ||
| throwOnError: true, | ||
| }); | ||
| }, | ||
| isNoDefaultValueProvidedError: (error: unknown) => | ||
| error instanceof NoDefaultValueProvided, | ||
| }; | ||
| const useCfAuth = "CLOUDFLARE_CF_AUTH" in process.env; | ||
| return { | ||
| auth: useCfAuth ? createCfAuth(context) : createWranglerAuth(context), | ||
| useCfAuth, | ||
| }; | ||
| } | ||
|
|
||
| export function getRemoteBindingsAuthHook( | ||
| auth: AsyncHook<CfAccount> | undefined, | ||
| accountId: Config["account_id"] | undefined, | ||
| profileDir: string | undefined, | ||
| logger: RemoteBindingsLogger | ||
| ): AsyncHook<CfAccount> { | ||
| if (auth) { | ||
| return auth; | ||
| } | ||
|
|
||
| const { auth: remoteBindingsAuth, useCfAuth } = | ||
| createRemoteBindingsAuth(logger); | ||
| const profileStore = useCfAuth | ||
| ? createCfProfileStore({ logger }) | ||
| : createWranglerProfileStore({ logger }); | ||
| const profile = profileStore.resolve({ | ||
| cwd: profileDir ?? process.cwd(), | ||
| }); | ||
| remoteBindingsAuth.setProfile(profile); | ||
|
|
||
| return async () => ({ | ||
| accountId: await remoteBindingsAuth.requireAuth( | ||
| accountId ? { account_id: accountId } : {} | ||
| ), | ||
| apiToken: remoteBindingsAuth.requireApiToken(), | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export { | ||
| maybeStartOrUpdateRemoteProxySession, | ||
| pickRemoteBindings, | ||
| } from "./maybe-start-or-update-session"; | ||
| export type { | ||
| RemoteBindingsContext, | ||
| RemoteProxySessionData, | ||
| WorkerConfigObject, | ||
| } from "./maybe-start-or-update-session"; | ||
| export { startRemoteProxySession } from "./start-remote-proxy-session"; | ||
| export type { RemoteBindingsLogger } from "./logger"; | ||
| export type { | ||
| RemoteProxySession, | ||
| StartRemoteProxySessionOptions, | ||
| } from "./start-remote-proxy-session"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import type { Logger, LoggerLevel } from "@cloudflare/workers-utils"; | ||
|
|
||
| export type RemoteBindingsLogger = Logger & { | ||
| loggerLevel: LoggerLevel; | ||
| console: NonNullable<Logger["console"]>; | ||
| }; | ||
|
|
||
| export let logger: RemoteBindingsLogger; | ||
|
|
||
| export function initLogger(value: RemoteBindingsLogger): void { | ||
| logger = value; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.