diff --git a/.changeset/clever-humans-happen.md b/.changeset/clever-humans-happen.md new file mode 100644 index 000000000000..090cffcae9d6 --- /dev/null +++ b/.changeset/clever-humans-happen.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +Refactor dev and publish commands to pass strict-null checks diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index f04fafb0663f..ff48553142a9 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -56,7 +56,7 @@ function Dev(props: DevProps): JSX.Element { "You cannot use the service worker format with a `public` directory." ); } - const port = props.port || 8787; + const port = props.port ?? 8787; const apiToken = getAPIToken(); const directory = useTmpDir(); @@ -98,7 +98,7 @@ function Dev(props: DevProps): JSX.Element { bindings={props.bindings} site={props.site} public={props.public} - port={props.port} + port={port} /> ) : ( { + local.current.stdout?.on("data", (data: Buffer) => { console.log(`${data.toString()}`); }); - local.current.stderr.on("data", (data: Buffer) => { + local.current.stderr?.on("data", (data: Buffer) => { console.error(`${data.toString()}`); const matches = /Debugger listening on (ws:\/\/127\.0\.0\.1:9229\/[A-Za-z0-9-]+)/.exec( @@ -361,7 +361,7 @@ function useCustomBuild( ): undefined | string { const [entry, setEntry] = useState( // if there's no build command, just return the expected entry - props.command ? null : expectedEntry + props.command || expectedEntry ); const { command, cwd, watch_dir } = props; useEffect(() => { @@ -465,26 +465,37 @@ function useEsbuild(props: { else { // nothing really changes here, so let's increment the id // to change the return object's identity - setBundle((previousBundle) => ({ - ...previousBundle, - id: previousBundle.id + 1, - })); + setBundle((previousBundle) => { + if (previousBundle === undefined) { + assert.fail( + "Rebuild triggered with no previous build available" + ); + } + return { ...previousBundle, id: previousBundle.id + 1 }; + }); } }, }, }); - const chunks = Object.entries(result.metafile.outputs).find( + // result.metafile is defined because of the `metafile: true` option above. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const metafile = result.metafile!; + const outputEntry = Object.entries(metafile.outputs).find( ([_path, { entryPoint }]) => - entryPoint === Object.keys(result.metafile.inputs)[0] + entryPoint === Object.keys(metafile.inputs)[0] ); // assumedly only one entry point - + if (outputEntry === undefined) { + throw new Error( + `Cannot find entry-point "${entry}" in generated bundle.` + ); + } setBundle({ id: 0, entry, - path: chunks[0], - type: chunks[1].exports.length > 0 ? "esm" : "commonjs", - exports: chunks[1].exports, + path: outputEntry[0], + type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs", + exports: outputEntry[1].exports, modules: moduleCollector.modules, }); } @@ -495,7 +506,7 @@ function useEsbuild(props: { // so this is a no-op error handler }); return () => { - result?.stop(); + result.stop?.(); }; }, [entry, destination, staticRoot, jsxFactory, jsxFragment]); return bundle; diff --git a/packages/wrangler/src/publish.ts b/packages/wrangler/src/publish.ts index cbbc9238518e..8c1efe1f1ffe 100644 --- a/packages/wrangler/src/publish.ts +++ b/packages/wrangler/src/publish.ts @@ -50,13 +50,18 @@ export default async function publish(props: Props): Promise { __path__, } = config; - const envRootObj = props.env ? config.env[props.env] || {} : config; + const envRootObj = + props.env && config.env ? config.env[props.env] || {} : config; assert( envRootObj.compatibility_date || props["compatibility-date"], "A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." ); + if (accountId === undefined) { + throw new Error("No account_id provided."); + } + const triggers = props.triggers || config.triggers?.crons; const routes = props.routes || config.routes; @@ -129,18 +134,24 @@ export default async function publish(props: Props): Promise { ...(jsxFragment && { jsxFragment }), }); - const chunks = Object.entries(result.metafile.outputs).find( - ([_path, { entryPoint }]) => - entryPoint === - (props.public - ? path.join(path.dirname(file), "static-asset-facade.js") - : Object.keys(result.metafile.inputs)[0]) + // result.metafile is defined because of the `metafile: true` option above. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const metafile = result.metafile!; + const expectedEntryPoint = props.public + ? path.join(path.dirname(file), "static-asset-facade.js") + : Object.keys(metafile.inputs)[0]; + const outputEntry = Object.entries(metafile.outputs).find( + ([, { entryPoint }]) => entryPoint === expectedEntryPoint ); - + if (outputEntry === undefined) { + throw new Error( + `Cannot find entry-point "${expectedEntryPoint}" in generated bundle.` + ); + } const { format } = props; const bundle = { - type: chunks[1].exports.length > 0 ? "esm" : "commonjs", - exports: chunks[1].exports, + type: outputEntry[1].exports.length > 0 ? "esm" : "commonjs", + exports: outputEntry[1].exports, }; // TODO: instead of bundling the facade with the worker, we should just bundle the worker and expose it as a module. @@ -157,13 +168,13 @@ export default async function publish(props: Props): Promise { return; } - const content = await readFile(chunks[0], { encoding: "utf-8" }); + const content = await readFile(outputEntry[0], { encoding: "utf-8" }); await destination.cleanup(); // if config.migrations // get current migration tag let migrations; - if ("migrations" in config) { + if (config.migrations !== undefined) { const scripts = await fetchResult<{ id: string; migration_tag: string }[]>( `/accounts/${accountId}/workers/scripts` ); @@ -199,15 +210,10 @@ export default async function publish(props: Props): Promise { } } - const assets = - props.public || props.site || props.config.site?.bucket // TODO: allow both - ? await syncAssets( - accountId, - scriptName, - props.public || props.site || props.config.site?.bucket, - false - ) - : { manifest: undefined, namespace: undefined }; + const assetPath = props.public || props.site || props.config.site?.bucket; // TODO: allow both + const assets = assetPath + ? await syncAssets(accountId, scriptName, assetPath, false) + : { manifest: undefined, namespace: undefined }; const bindings: CfWorkerInit["bindings"] = { kv_namespaces: envRootObj.kv_namespaces?.concat( @@ -223,7 +229,7 @@ export default async function publish(props: Props): Promise { const worker: CfWorkerInit = { name: scriptName, main: { - name: path.basename(chunks[0]), + name: path.basename(outputEntry[0]), content: content, type: bundle.type === "esm" ? "esm" : "commonjs", },