Skip to content

Commit

Permalink
feat(wrangler): Add remote mode support for Workers + Assets
Browse files Browse the repository at this point in the history
  • Loading branch information
CarmenPopoviciu committed Dec 3, 2024
1 parent 4f1a46e commit 5b20edd
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 57 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-tips-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Add Workers + Assets support in `wrangler dev --remote`
24 changes: 24 additions & 0 deletions packages/wrangler/e2e/__snapshots__/dev.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ exports[`basic js dev: 'wrangler dev --remote' > --test-scheduled works with wra

exports[`basic js dev: 'wrangler dev --remote' > --test-scheduled works with wrangler dev --remote > no custom build 1`] = `"Ran scheduled event"`;

exports[`basic js dev: 'wrangler dev --remote' > Workers + Assets > can modify User Worker during wrangler dev --remote 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev --remote' > Workers + Assets > can modify User Worker during wrangler dev --remote 2`] = `"Updated Worker!"`;

exports[`basic js dev: 'wrangler dev --remote' > Workers + Assets > can modify assets during wrangler dev --remote 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev --remote' > Workers + Assets > can modify assets during wrangler dev --remote 2`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev --remote' > can modify Worker during wrangler dev --remote 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev --remote' > can modify Worker during wrangler dev --remote 2`] = `"Updated Worker! value"`;

exports[`basic js dev: 'wrangler dev --remote' > can modify worker during wrangler dev --remote 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev --remote' > can modify worker during wrangler dev --remote 2`] = `"Updated Worker! value"`;
Expand All @@ -14,6 +26,18 @@ exports[`basic js dev: 'wrangler dev' > --test-scheduled works with wrangler dev

exports[`basic js dev: 'wrangler dev' > --test-scheduled works with wrangler dev > no custom build 1`] = `"Ran scheduled event"`;

exports[`basic js dev: 'wrangler dev' > Workers + Assets > can modify User Worker during wrangler dev 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev' > Workers + Assets > can modify User Worker during wrangler dev 2`] = `"Updated Worker!"`;

exports[`basic js dev: 'wrangler dev' > Workers + Assets > can modify assets during wrangler dev 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev' > Workers + Assets > can modify assets during wrangler dev 2`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev' > can modify Worker during wrangler dev 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev' > can modify Worker during wrangler dev 2`] = `"Updated Worker! value"`;

exports[`basic js dev: 'wrangler dev' > can modify worker during wrangler dev 1`] = `"Hello World!"`;

exports[`basic js dev: 'wrangler dev' > can modify worker during wrangler dev 2`] = `"Updated Worker! value"`;
Expand Down
100 changes: 99 additions & 1 deletion packages/wrangler/e2e/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ it("can import URL from 'url' in node_compat mode", async () => {
describe.each([{ cmd: "wrangler dev" }, { cmd: "wrangler dev --remote" }])(
"basic js dev: $cmd",
({ cmd }) => {
it(`can modify worker during ${cmd}`, async () => {
it(`can modify Worker during ${cmd}`, async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed({
"wrangler.toml": dedent`
Expand Down Expand Up @@ -207,6 +207,104 @@ describe.each([{ cmd: "wrangler dev" }, { cmd: "wrangler dev --remote" }])(
await worker.readUntil(/Event triggered/);
});
});

describe("Workers + Assets", () => {
it(`can modify User Worker during ${cmd}`, async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed({
"wrangler.toml": dedent`
name = "${workerName}"
main = "src/index.ts"
compatibility_date = "2023-01-01"
compatibility_flags = ["nodejs_compat"]
[assets]
directory = "public"
`,
"src/index.ts": dedent`
export default {
fetch(request) {
return new Response("Hello World!")
}
}`,
"public/readme.md": dedent`
Welcome to Workers + Assets readme!`,
"package.json": dedent`
{
"name": "worker",
"version": "0.0.0",
"private": true
}
`,
});
const worker = helper.runLongLived(cmd);

const { url } = await worker.waitForReady();

await expect(
fetch(url).then((r) => r.text())
).resolves.toMatchSnapshot();

await helper.seed({
"src/index.ts": dedent`
export default {
fetch(request, env) {
return new Response("Updated Worker!")
}
}`,
});

await worker.waitForReload();

await expect(fetchText(url)).resolves.toMatchSnapshot();
});

it(`can modify assets during ${cmd}`, async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed({
"wrangler.toml": dedent`
name = "${workerName}"
main = "src/index.ts"
compatibility_date = "2023-01-01"
compatibility_flags = ["nodejs_compat"]
[assets]
directory = "public"
`,
"src/index.ts": dedent`
export default {
fetch(request) {
return new Response("Hello World!")
}
}`,
"public/readme.md": dedent`
Welcome to Workers + Assets readme!`,
"package.json": dedent`
{
"name": "worker",
"version": "0.0.0",
"private": true
}
`,
});
const worker = helper.runLongLived(cmd);

const { url } = await worker.waitForReady();

await expect(
fetch(url).then((r) => r.text())
).resolves.toMatchSnapshot();

await helper.seed({
"public/readme.md": dedent`
Welcome to updated Workers + Assets readme!`,
});

await worker.waitForReload();

await expect(fetchText(url)).resolves.toMatchSnapshot();
});
});
}
);

Expand Down
21 changes: 0 additions & 21 deletions packages/wrangler/src/__tests__/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1732,27 +1732,6 @@ describe.sequential("wrangler dev", () => {
)
);
});

it("should error if --assets and --remote are used together", async () => {
fs.mkdirSync("public");
await expect(
runWrangler("dev --assets public --remote")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use assets in remote mode. Workers with assets are only supported in local mode. Please use \`wrangler dev\`.]`
);
});

it("should error if config.assets and --remote are used together", async () => {
writeWranglerConfig({
assets: { directory: "./public" },
});
fs.mkdirSync("public");
await expect(
runWrangler("dev --remote")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use assets in remote mode. Workers with assets are only supported in local mode. Please use \`wrangler dev\`.]`
);
});
});

describe("--inspect", () => {
Expand Down
6 changes: 0 additions & 6 deletions packages/wrangler/src/api/startDevWorker/ConfigController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,6 @@ async function resolveConfig(
);
}

if (resolved.assets && resolved.dev.remote) {
throw new UserError(
"Cannot use assets in remote mode. Workers with assets are only supported in local mode. Please use `wrangler dev`."
);
}

validateAssetsArgsAndConfig(resolved);

const services = extractBindingsOfType("service", resolved.bindings);
Expand Down
46 changes: 29 additions & 17 deletions packages/wrangler/src/api/startDevWorker/RemoteRuntimeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import type {
} from "./events";
import type { Trigger } from "./types";

type CreateRemoteWorkerInitProps = Parameters<typeof createRemoteWorkerInit>[0];

export class RemoteRuntimeController extends RuntimeController {
#abortController = new AbortController();

Expand Down Expand Up @@ -60,40 +62,49 @@ export class RemoteRuntimeController extends RuntimeController {
}

async #previewToken(
props: Parameters<typeof createRemoteWorkerInit>[0] &
props: Omit<CreateRemoteWorkerInitProps, "name"> &
Partial<Pick<CreateRemoteWorkerInitProps, "name">> &
Parameters<typeof getWorkerAccountAndContext>[0]
): Promise<CfPreviewToken | undefined> {
if (!this.#session) {
return;
}

try {
const { workerAccount, workerContext } = await getWorkerAccountAndContext(
{
accountId: props.accountId,
env: props.env,
legacyEnv: props.legacyEnv,
host: props.host,
routes: props.routes,
sendMetrics: props.sendMetrics,
configPath: props.configPath,
}
);

const scriptId =
props.name ||
(workerContext.zone
? this.#session.id
: this.#session.host.split(".")[0]);

const init = await createRemoteWorkerInit({
bundle: props.bundle,
modules: props.modules,
accountId: props.accountId,
name: props.name,
name: scriptId,
legacyEnv: props.legacyEnv,
env: props.env,
isWorkersSite: props.isWorkersSite,
assets: props.assets,
legacyAssetPaths: props.legacyAssetPaths,
format: props.format,
bindings: props.bindings,
compatibilityDate: props.compatibilityDate,
compatibilityFlags: props.compatibilityFlags,
});

const { workerAccount, workerContext } = await getWorkerAccountAndContext(
{
accountId: props.accountId,
env: props.env,
legacyEnv: props.legacyEnv,
host: props.host,
routes: props.routes,
sendMetrics: props.sendMetrics,
configPath: props.configPath,
}
);
if (!this.#session) {
return;
}

const workerPreviewToken = await createWorkerPreview(
init,
workerAccount,
Expand Down Expand Up @@ -173,6 +184,7 @@ export class RemoteRuntimeController extends RuntimeController {
legacyEnv: !config.legacy?.enableServiceEnvironments,
env: config.env,
isWorkersSite: config.legacy?.site !== undefined,
assets: config.assets,
legacyAssetPaths: config.legacy?.site?.bucket
? {
baseDirectory: config.legacy?.site?.bucket,
Expand Down
15 changes: 6 additions & 9 deletions packages/wrangler/src/dev/create-worker-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import { logger } from "../logger";
import { ParseError, parseJSON } from "../parse";
import { getAccessToken } from "../user/access";
import { isAbortError } from "../utils/isAbortError";
import type {
CfWorkerContext,
CfWorkerInit,
} from "../deployment-bundle/worker";
import type { CfWorkerContext } from "../deployment-bundle/worker";
import type { ApiCredentials } from "../user";
import type { CfWorkerInitWithName } from "./remote";
import type { HeadersInit } from "undici";

/**
Expand Down Expand Up @@ -227,18 +225,17 @@ export async function createPreviewSession(
*/
async function createPreviewToken(
account: CfAccount,
worker: CfWorkerInit,
worker: CfWorkerInitWithName,
ctx: CfWorkerContext,
session: CfPreviewSession,
abortSignal: AbortSignal
): Promise<CfPreviewToken> {
const { value, host, inspectorUrl, prewarmUrl } = session;
const { accountId } = account;
const scriptId = worker.name || (ctx.zone ? session.id : host.split(".")[0]);
const url =
ctx.env && !ctx.legacyEnv
? `/accounts/${accountId}/workers/services/${scriptId}/environments/${ctx.env}/edge-preview`
: `/accounts/${accountId}/workers/scripts/${scriptId}/edge-preview`;
? `/accounts/${accountId}/workers/services/${worker.name}/environments/${ctx.env}/edge-preview`
: `/accounts/${accountId}/workers/scripts/${worker.name}/edge-preview`;

const mode: CfPreviewMode = ctx.zone
? {
Expand Down Expand Up @@ -303,7 +300,7 @@ async function createPreviewToken(
* const {value, host} = await createWorker(init, acct);
*/
export async function createWorkerPreview(
init: CfWorkerInit,
init: CfWorkerInitWithName,
account: CfAccount,
ctx: CfWorkerContext,
session: CfPreviewSession,
Expand Down
23 changes: 20 additions & 3 deletions packages/wrangler/src/dev/remote.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from "node:assert";
import path from "node:path";
import { syncAssets } from "../assets";
import { printBundleSize } from "../deployment-bundle/bundle-reporter";
import { getBundleType } from "../deployment-bundle/bundle-type";
import { withSourceURLs } from "../deployment-bundle/source-url";
Expand All @@ -10,6 +11,7 @@ import { syncLegacyAssets } from "../sites";
import { requireApiToken } from "../user";
import { isAbortError } from "../utils/isAbortError";
import { getZoneIdForPreview } from "../zones";
import type { AssetsOptions } from "../assets";
import type { Route } from "../config/environment";
import type {
CfModule,
Expand Down Expand Up @@ -79,14 +81,18 @@ export function handlePreviewSessionCreationError(
}
}

export type CfWorkerInitWithName = Required<Pick<CfWorkerInit, "name">> &
CfWorkerInit;

export async function createRemoteWorkerInit(props: {
bundle: EsbuildBundle;
modules: CfModule[];
accountId: string;
name: string | undefined;
name: string;
legacyEnv: boolean | undefined;
env: string | undefined;
isWorkersSite: boolean;
assets: AssetsOptions | undefined;
legacyAssetPaths: LegacyAssetPaths | undefined;
format: CfScriptFormat;
bindings: CfWorkerInit["bindings"];
Expand Down Expand Up @@ -130,7 +136,11 @@ export async function createRemoteWorkerInit(props: {
});
}

const init: CfWorkerInit = {
const assetsJwt = props.assets
? await syncAssets(props.accountId, props.name, props.assets.directory)
: undefined;

const init: CfWorkerInitWithName = {
name: props.name,
main: {
name: path.basename(props.bundle.path),
Expand Down Expand Up @@ -161,10 +171,17 @@ export async function createRemoteWorkerInit(props: {
keepSecrets: true,
logpush: false,
sourceMaps: undefined,
assets:
props.assets && assetsJwt
? {
jwt: assetsJwt,
routingConfig: props.assets.routingConfig,
assetConfig: props.assets.assetConfig,
}
: undefined,
placement: undefined, // no placement in dev
tail_consumers: undefined, // no tail consumers in dev - TODO revisit?
limits: undefined, // no limits in preview - not supported yet but can be added
assets: undefined, // no remote mode for assets
observability: undefined, // no observability in dev
};

Expand Down

0 comments on commit 5b20edd

Please sign in to comment.