diff --git a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/compare-outputs.ts b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/compare-outputs.ts index 612ac6cf01bbb..e254560cf3007 100644 --- a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/compare-outputs.ts +++ b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/compare-outputs.ts @@ -1,6 +1,7 @@ +import childProcess from "node:child_process"; import fs from "node:fs"; import path from "node:path"; -import process from "node:process"; +import { Writable as WritableStream } from "node:stream"; import { execa } from "execa"; import { mockConsoleMethods } from "wrangler/src/__tests__/helpers/mock-console"; import { runWrangler as runWrangler2 } from "wrangler/src/__tests__/helpers/run-wrangler"; @@ -9,6 +10,7 @@ import writeWranglerToml from "wrangler/src/__tests__/helpers/write-wrangler-tom import { PATH_TO_PLUGIN } from "./constants"; import { mockSubDomainRequest } from "./mock-subdomain-request"; import { mockUploadWorkerRequest } from "./mock-upload-worker-request"; +import { pipe } from "./pipe"; import { runWrangler1 } from "./run-wrangler-1"; import { writePackageJson } from "./write-package-json"; import { writeWebpackConfig } from "./write-webpack-config"; @@ -16,6 +18,7 @@ import type { CoreProperties } from "@schemastore/package"; import type { ExecaError, ExecaReturnValue } from "execa"; import type webpack from "webpack"; import type { RawConfig } from "wrangler/src/config"; +// import process from "node:process"; type PartialWranglerConfig = Omit; type PartialWorker = Omit< @@ -81,6 +84,7 @@ export async function compareOutputs({ mockUploadWorkerRequest({ expectedType: worker?.type, + expectedName: "script.js", }); mockSubDomainRequest(); @@ -111,18 +115,43 @@ export async function compareOutputs({ }, }); - await execa("npm", ["install"]); + await execa("npm", ["install"], { + cwd: wrangler2Dir, + }); + + let wrangler2result: Error | undefined = undefined; + + // we need to capture webpack output + const stdout = new WritableStream({ + write: pipe((message) => { + if (!message.includes("WARNING")) { + console.log(message); + } else { + const [output, warning] = message.split("WARNING"); + console.log(output); + console.warn(`WARNING ${warning}`); + } + }), + }); + const stderr = new WritableStream({ + write: pipe(console.error), + }); - let wrangler2result: Error | undefined; try { - await runWrangler2("publish"); + await withCapturedChildProcessOutput(() => runWrangler2("publish"), { + stdout, + stderr, + }); } catch (e) { - const error = e as Error; - if (isAssertionError(error)) { - throw error; - } else { - wrangler2result = error; - } + wrangler2result = e as Error; + } finally { + process.stdout.unpipe(stdout); + process.stderr.unpipe(stderr); + } + + // an assertion failed, so we should throw + if (wrangler2result !== undefined && isAssertionError(wrangler2result)) { + throw wrangler2result; } const wrangler2 = { @@ -157,3 +186,31 @@ const clearConsole = () => { */ const isAssertionError = (e: Error) => Object.prototype.hasOwnProperty.bind(e)("matcherResult"); + +async function withCapturedChildProcessOutput( + fn: () => T | Promise, + { stdout, stderr }: { stdout: WritableStream; stderr: WritableStream } +): Promise { + const { spawn } = childProcess; + let process: childProcess.ChildProcess | undefined = undefined; + const childProcessMock = jest + .spyOn(childProcess, "spawn") + .mockImplementation((command, args, options) => { + process = spawn(command, args, options); + if (process.stdout !== null && process.stderr !== null) { + process.stdout.pipe(stdout); + process.stderr.pipe(stderr); + } + return process; + }); + + try { + return await fn(); + } finally { + if (process.stdout !== null && process.stderr !== null) { + process.stdout.unpipe(stdout); + process.stderr.unpipe(stderr); + } + childProcessMock.mockRestore(); + } +} diff --git a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/mock-upload-worker-request.ts b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/mock-upload-worker-request.ts index c07225ab5046e..b80f3032ea356 100644 --- a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/mock-upload-worker-request.ts +++ b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/mock-upload-worker-request.ts @@ -16,6 +16,7 @@ export function mockUploadWorkerRequest( expectedMigrations?: CfWorkerInit["migrations"]; env?: string; legacyEnv?: boolean; + expectedName?: string; } = {} ) { const { @@ -29,6 +30,7 @@ export function mockUploadWorkerRequest( env = undefined, legacyEnv = false, expectedMigrations, + expectedName = "index.js", } = options; setMockResponse( env && !legacyEnv @@ -46,7 +48,7 @@ export function mockUploadWorkerRequest( expect(queryParams.get("available_on_subdomain")).toEqual("true"); const formBody = body as FormData; if (expectedEntry !== undefined) { - expect(await (formBody.get("index.js") as File).text()).toMatch( + expect(await (formBody.get(expectedName) as File).text()).toMatch( expectedEntry ); } @@ -54,9 +56,9 @@ export function mockUploadWorkerRequest( formBody.get("metadata") as string ) as WorkerMetadata; if (expectedType === "esm") { - expect(metadata.main_module).toEqual("index.js"); + expect(metadata.main_module).toEqual(expectedName); } else { - expect(metadata.body_part).toEqual("script.js"); // ? "index.js" + expect(metadata.body_part).toEqual(expectedName); } if ("expectedBindings" in options) { expect(metadata.bindings).toEqual(expectedBindings); diff --git a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/pipe.ts b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/pipe.ts new file mode 100644 index 0000000000000..5e9a68a8173a1 --- /dev/null +++ b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/pipe.ts @@ -0,0 +1,73 @@ +import { PATH_TO_WRANGLER } from "./constants"; +import type { WritableOptions } from "node:stream"; + +/** + * Helper utility for use in piping child process outputs + * into `console` functions + * + * @param logger Any function that takes a string as input + * @returns an implementaion of WritableOptions.write + */ +export function pipe( + logger: (message: string) => void +): WritableOptions["write"] { + return (chunk, encoding, callback) => { + let error: Error | null | undefined = undefined; + + try { + const message = cleanMessage(stringifyChunk(chunk, encoding)); + if (message !== "") { + logger(message); + } + } catch (e) { + if (e === null || e === undefined || e instanceof Error) { + error = e; + } else { + throw new Error(`Encountered unexpected error ${e}`); + } + } + + callback(error); + }; +} +/** + * Even though they're not supposed to, sometimes `encoding` will be "buffer" + * Which just means, like...it's a buffer. It really should be "utf-8" instead + * but whatever. + */ +const stringifyChunk = ( + chunk: unknown, + encoding: BufferEncoding | "buffer" +): string => { + if (chunk instanceof Buffer) { + if (encoding !== "buffer") { + return chunk.toString(encoding); + } + + return chunk.toString(); + } + + if (typeof chunk === "string") { + return chunk; + } + + throw new Error("Unsure what type of chunk this is."); +}; +/** + * Find-and-replace various things in console output that vary between + * runs with standardized text + */ +export const cleanMessage = (message: string): string => + message + .replaceAll(/^.*debugger.*$/gim, "") // remove debugger statements + .replaceAll(/\d+ms/gm, "[timing]") // standardize timings + .replaceAll(process.cwd(), "[temp dir]") // standardize directories + .replaceAll(PATH_TO_WRANGLER, "[wrangler 1]") // standardize calls to wrangler 1 + .replaceAll(/found .+ vulnerabilities/gm, "found [some] vulnerabilities") // vuln counts + .replaceAll( + // remove specific paths to node, wranglerjs, and output file + /(Error: failed to execute `)(\S*node\S*) (\S*wranglerjs\S*) \S*(--output-file=)(\S+)(.+)/gm, + '$1"node" "wranglerjs" "$4[file]"$6' + ) + .replaceAll(/^Built at: .+$/gim, "Built at: [time]") + .trim(); diff --git a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/run-wrangler-1.ts b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/run-wrangler-1.ts index 3c489d57d578e..43436ef0d8fcb 100644 --- a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/run-wrangler-1.ts +++ b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/helpers/run-wrangler-1.ts @@ -1,8 +1,8 @@ import { Writable as WritableStream } from "node:stream"; import { execaCommand } from "execa"; import { PATH_TO_WRANGLER } from "./constants"; +import { pipe, cleanMessage } from "./pipe"; import type { ExecaError } from "execa"; -import type { WritableOptions } from "node:stream"; export async function runWrangler1(command?: string) { const stdout = new WritableStream({ @@ -29,73 +29,3 @@ export async function runWrangler1(command?: string) { throw error; } } - -/** - * Helper utility for use in piping child process outputs - * into `console` functions - * - * @param logger Any function that takes a string as input - * @returns an implementaion of WritableOptions.write - */ -function pipe(logger: (message: string) => void): WritableOptions["write"] { - return (chunk, encoding, callback) => { - let error: Error | null | undefined = undefined; - - try { - const message = cleanMessage(stringifyChunk(chunk, encoding)); - if (message !== "") { - logger(message); - } - } catch (e) { - if (e === null || e === undefined || e instanceof Error) { - error = e; - } else { - throw new Error(`Encountered unexpected error ${e}`); - } - } - - callback(error); - }; -} - -/** - * Even though they're not supposed to, sometimes `encoding` will be "buffer" - * Which just means, like...it's a buffer. It really should be "utf-8" instead - * but whatever. - */ -const stringifyChunk = ( - chunk: unknown, - encoding: BufferEncoding | "buffer" -): string => { - if (chunk instanceof Buffer) { - if (encoding !== "buffer") { - return chunk.toString(encoding); - } - - return chunk.toString(); - } - - if (typeof chunk === "string") { - return chunk; - } - - throw new Error("Unsure what type of chunk this is."); -}; - -/** - * Find-and-replace various things in console output that vary between - * runs with standardized text - */ -const cleanMessage = (message: string): string => - message - .replaceAll(/^.*debugger.*$/gim, "") // remove debugger statements - .replaceAll(/\d+ms/gm, "[timing]") // standardize timings - .replaceAll(process.cwd(), "[temp dir]") // standardize directories - .replaceAll(PATH_TO_WRANGLER, "[wrangler 1]") // standardize calls to wrangler 1 - .replaceAll(/found .+ vulnerabilities/gm, "found [some] vulnerabilities") // vuln counts - .replaceAll( - // remove specific paths to node, wranglerjs, and output file - /(Error: failed to execute `)(\S*node\S*) (\S*wranglerjs\S*) \S*(--output-file=)(\S+)(.+)/gm, - '$1"node" "wranglerjs" "$4[file]"$6' - ) - .trim(); diff --git a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/index.spec.ts b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/index.spec.ts index dcf1c9681568b..eab76eee06bd0 100644 --- a/packages/wranglerjs-compat-webpack-plugin/src/__tests__/index.spec.ts +++ b/packages/wranglerjs-compat-webpack-plugin/src/__tests__/index.spec.ts @@ -48,10 +48,30 @@ it("works with a basic webpack config", async () => { expect(wrangler2.std.out).toMatchInlineSnapshot(` "running: npm run build + > build + > webpack + Hash: e96932fc5c1ce19ddd05 + Version: webpack 4.46.0 + Time: [timing] + Built at: [time] + Asset Size Chunks Chunk Names + worker.js 1020 bytes 0 main + Entrypoint main = worker.js + [0] ./index.js + 1 modules 163 bytes {0} [built] + | ./index.js 140 bytes [built] + | ./another.js 23 bytes [built] + + Uploaded test-name (TIMINGS) Published test-name (TIMINGS) test-name.test-sub-domain.workers.dev" `); - expect(wrangler2.std.err).toMatchInlineSnapshot(`""`); - expect(wrangler2.std.warn).toMatchInlineSnapshot(`""`); + expect(wrangler2.std.err).toMatchInlineSnapshot( + `"You should set \`output.filename\` to \\"worker.js\\" in your webpack config."` + ); + expect(wrangler2.std.warn).toMatchInlineSnapshot(` + "WARNING in configuration + The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. + You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/" + `); });