Skip to content
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

feat(wrangler): Add remote mode support for Workers + Assets #7380

Merged
merged 1 commit into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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(
Copy link
Contributor Author

@CarmenPopoviciu CarmenPopoviciu Nov 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moving this code higher in the call chain so we can extract the Worker scriptId and pass it into createRemoteWorkerInit. createRemoteWorkerInit needs the Worker name to be defined so that it can make the assets upload API call, which would otherwise have unintended side effects

The logic of this code block has not changed!

{
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 @@ -172,6 +183,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.assets.directory, props.name)
: 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
Loading