Skip to content
Draft
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/wrangler-build-write-workerd-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

wrangler build: add a new flag --write-workerd-config [path] to generate a workerd capnp configuration file for the built Worker, suitable for running with the workerd binary. The path may be a file or a directory (defaults to workerd.capnp).
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { execa } from "execa";
import { runInTempDir } from "./helpers/run-in-tmp";

describe("wrangler build --write-workerd-config", () => {
it("writes a workerd capnp config to the specified path", async () => {
runInTempDir();
writeFileSync(
join(process.cwd(), "wrangler.toml"),
`name = "test-worker"\nmain = "index.js"\ncompatibility_date = "2024-01-01"\n`
);
writeFileSync(
join(process.cwd(), "index.js"),
`export default { fetch() { return new Response("ok"); } }`
);

await execa(
"node",
[
join(__dirname, "..", "..", "bin", "wrangler.js"),
"build",
"--write-workerd-config",
"out.capnp",
],
{
cwd: process.cwd(),
timeout: 120_000,
stdio: "pipe",
}
);

const outPath = join(process.cwd(), "out.capnp");
expect(existsSync(outPath)).toBe(true);
const buf = readFileSync(outPath);
expect(buf.byteLength).toBeGreaterThan(0);
}, 120_000);
});
16 changes: 14 additions & 2 deletions packages/wrangler/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,26 @@ export const buildCommand = createCommand({
printBanner: false,
provideConfig: false,
},
args: {
"write-workerd-config": {
type: "string",
describe:
"Path to write a workerd capnp config for running the built worker in workerd",
requiresArg: true,
},
},
async handler(buildArgs) {
const { wrangler } = createCLIParser([
const argv = [
"deploy",
"--dry-run",
"--outdir=dist",
...(buildArgs.env ? ["--env", buildArgs.env] : []),
...(buildArgs.config ? ["--config", buildArgs.config] : []),
]);
...(buildArgs.writeWorkerdConfig
? ["--write-workerd-config", buildArgs.writeWorkerdConfig as string]
: []),
];
const { wrangler } = createCLIParser(argv);
await wrangler.parse();
},
});
16 changes: 10 additions & 6 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ export default async function deploy(props: Props): Promise<{
versionId: string | null;
workerTag: string | null;
targets?: string[];
emittedEntryPath: string;
}> {
const deployConfirm = getDeployConfirmFunction(props.strict);

Expand Down Expand Up @@ -422,23 +423,23 @@ export default async function deploy(props: Props): Promise<{
"Deploying the Worker will override the remote configuration with your local one."
);
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath: "" };
}
}
} else {
logger.warn(
`You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
);
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath: "" };
}
}
} else if (script.last_deployed_from === "api") {
logger.warn(
`You are about to publish a Workers Service that was last updated via the script API.\nEdits that have been made via the script API will be overridden by your local code and config.`
);
if (!(await deployConfirm("Would you like to continue?"))) {
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath: "" };
}
}
} catch (e) {
Expand Down Expand Up @@ -537,7 +538,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
);
if (!yes) {
cancel("Aborting deploy...");
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath: "" };
}
}

Expand Down Expand Up @@ -574,6 +575,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
}

let sourceMapSize;
let emittedEntryPath = "";
const normalisedContainerConfig = await getNormalizedContainerOptions(
config,
props
Expand Down Expand Up @@ -671,6 +673,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
: module.content.byteLength;
dependencies[modulePath] = { bytesInOutput };
}
emittedEntryPath = resolvedEntryPointPath;

const content = readFileSync(resolvedEntryPointPath, {
encoding: "utf-8",
Expand Down Expand Up @@ -1109,7 +1112,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m

if (props.dryRun) {
logger.log(`--dry-run: exiting now.`);
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath };
}

const uploadMs = Date.now() - start;
Expand All @@ -1119,7 +1122,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
// Early exit for WfP since it doesn't need the below code
if (props.dispatchNamespace !== undefined) {
deployWfpUserWorker(props.dispatchNamespace, versionId);
return { versionId, workerTag };
return { versionId, workerTag, emittedEntryPath };
}

if (normalisedContainerConfig.length) {
Expand All @@ -1145,6 +1148,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
sourceMapSize,
versionId,
workerTag,
emittedEntryPath,
targets: targets ?? [],
};
}
Expand Down
138 changes: 98 additions & 40 deletions packages/wrangler/src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { getRules } from "../utils/getRules";
import { getScriptName } from "../utils/getScriptName";
import { isLegacyEnv } from "../utils/isLegacyEnv";
import deploy from "./deploy";
import type { Config } from "miniflare";

export const deployCommand = createCommand({
metadata: {
Expand Down Expand Up @@ -180,6 +181,12 @@ export const deployCommand = createCommand({
describe: "Don't actually deploy",
type: "boolean",
},
"write-workerd-config": {
describe:
"Path to write a workerd capnp config for running the built worker in workerd (only with --dry-run)",
type: "string",
requiresArg: true,
},
metafile: {
describe:
"Path to output build metadata from esbuild. If flag is used without a path, defaults to 'bundle-meta.json' inside the directory specified by --outdir.",
Expand Down Expand Up @@ -350,46 +357,47 @@ export const deployCommand = createCommand({
config.configPath
);
}
const { sourceMapSize, versionId, workerTag, targets } = await deploy({
config,
accountId,
name,
rules: getRules(config),
entry,
env: args.env,
compatibilityDate: args.latest
? formatCompatibilityDate(new Date())
: args.compatibilityDate,
compatibilityFlags: args.compatibilityFlags,
vars: cliVars,
defines: cliDefines,
alias: cliAlias,
triggers: args.triggers,
jsxFactory: args.jsxFactory,
jsxFragment: args.jsxFragment,
tsconfig: args.tsconfig,
routes: args.routes,
domains: args.domains,
assetsOptions,
legacyAssetPaths: siteAssetPaths,
legacyEnv: isLegacyEnv(config),
minify: args.minify,
isWorkersSite: Boolean(args.site || config.site),
outDir: args.outdir,
outFile: args.outfile,
dryRun: args.dryRun,
metafile: args.metafile,
noBundle: !(args.bundle ?? !config.no_bundle),
keepVars: args.keepVars,
logpush: args.logpush,
uploadSourceMaps: args.uploadSourceMaps,
oldAssetTtl: args.oldAssetTtl,
projectRoot,
dispatchNamespace: args.dispatchNamespace,
experimentalAutoCreate: args.experimentalAutoCreate,
containersRollout: args.containersRollout,
strict: args.strict,
});
const { sourceMapSize, versionId, workerTag, targets, emittedEntryPath } =
await deploy({
config,
accountId,
name,
rules: getRules(config),
entry,
env: args.env,
compatibilityDate: args.latest
? formatCompatibilityDate(new Date())
: args.compatibilityDate,
compatibilityFlags: args.compatibilityFlags,
vars: cliVars,
defines: cliDefines,
alias: cliAlias,
triggers: args.triggers,
jsxFactory: args.jsxFactory,
jsxFragment: args.jsxFragment,
tsconfig: args.tsconfig,
routes: args.routes,
domains: args.domains,
assetsOptions,
legacyAssetPaths: siteAssetPaths,
legacyEnv: isLegacyEnv(config),
minify: args.minify,
isWorkersSite: Boolean(args.site || config.site),
outDir: args.outdir,
outFile: args.outfile,
dryRun: args.dryRun,
metafile: args.metafile,
noBundle: !(args.bundle ?? !config.no_bundle),
keepVars: args.keepVars,
logpush: args.logpush,
uploadSourceMaps: args.uploadSourceMaps,
oldAssetTtl: args.oldAssetTtl,
projectRoot,
dispatchNamespace: args.dispatchNamespace,
experimentalAutoCreate: args.experimentalAutoCreate,
containersRollout: args.containersRollout,
strict: args.strict,
});

writeOutput({
type: "deploy",
Expand All @@ -413,6 +421,56 @@ export const deployCommand = createCommand({
sendMetrics: config.send_metrics,
}
);

if (args.dryRun && args.writeWorkerdConfig) {
const outPathArg = args.writeWorkerdConfig as string;
const fs = await import("node:fs/promises");
const { serializeConfig } = await import("miniflare");

const serviceName = name ?? "worker";

const chosenEntry = emittedEntryPath;
if (!chosenEntry) {
throw new UserError(
"Failed to determine emitted bundle entry to generate workerd config. Please ensure bundling succeeded."
);
}

const workerdConfig: Config = {
services: [
{
name: serviceName,
worker: {
modules: [
{
name: path.basename(chosenEntry),
esModule: chosenEntry,
},
],
compatibilityDate: args.latest
? formatCompatibilityDate(new Date())
: args.compatibilityDate ?? config.compatibility_date,
compatibilityFlags:
args.compatibilityFlags ?? config.compatibility_flags,
},
},
],
};

let outputPath = outPathArg;
try {
const stat = await fs.stat(outPathArg);
if (stat.isDirectory()) {
outputPath = path.join(outPathArg, "workerd.capnp");
}
} catch {
await fs.mkdir(path.dirname(outPathArg), { recursive: true });
}

const buf = serializeConfig(workerdConfig);
await fs.writeFile(outputPath, buf);
logger.log(`Wrote workerd config to ${path.resolve(outputPath)}`);
}
},
});

Expand Down
Loading