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

refactor: remove process.exit() from the pages code #747

Merged
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
refactor: remove process.exit() from the pages code
This enables simpler testing, as we do not have to spawn new child processes
to avoid the `process.exit()` from killing the jest process.

As part of the refactor, some of the `Error` classes have been moved to a
shared `errors.ts` file.
  • Loading branch information
petebacondarwin committed Apr 2, 2022
commit 60123dd23e0da5d8b039bc439d29516dd87375cf
11 changes: 11 additions & 0 deletions .changeset/polite-lemons-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"wrangler": patch
---

refactor: remove `process.exit()` from the pages code

This enables simpler testing, as we do not have to spawn new child processes
to avoid the `process.exit()` from killing the jest process.

As part of the refactor, some of the `Error` classes have been moved to a
shared `errors.ts` file.
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"pgrep",
"PKCE",
"Positionals",
"scandir",
"selfsigned",
"textfile",
"tsbuildinfo",
Expand All @@ -28,7 +29,12 @@
"websockets",
"xxhash"
],
"cSpell.ignoreWords": ["TESTTEXTBLOBNAME", "TESTWASMNAME", "yxxx"],
"cSpell.ignoreWords": [
"TESTTEXTBLOBNAME",
"TESTWASMNAME",
"extensionless",
"yxxx"
],
"eslint.runtime": "node",
"files.trimTrailingWhitespace": true
}
8 changes: 4 additions & 4 deletions packages/wrangler/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,14 +845,14 @@ describe("wrangler", () => {
it("should throw an error if the deprecated command is used with positional arguments", async () => {
await expect(runWrangler("preview GET")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
The \`wrangler preview\` command has been deprecated.
Try using \`wrangler dev\` to to try out a worker during development.
"
`);
await expect(runWrangler(`preview GET "SomeBody"`)).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
The \`wrangler preview\` command has been deprecated.
Try using \`wrangler dev\` to to try out a worker during development.
"
Expand Down Expand Up @@ -970,15 +970,15 @@ describe("wrangler", () => {
it("should print a deprecation message for 'generate'", async () => {
await runWrangler("generate").catch((err) => {
expect(err.message).toMatchInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler generate\` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#generate for alternatives"
`);
});
});
it("should print a deprecation message for 'build'", async () => {
await runWrangler("build").catch((err) => {
expect(err.message).toMatchInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler build\` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#build for alternatives"
`);
});
Expand Down
66 changes: 21 additions & 45 deletions packages/wrangler/src/__tests__/pages.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import { execaSync } from "execa";
import { getPackageManager } from "../package-manager";
import { mockConsoleMethods } from "./helpers/mock-console";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import type { PackageManager } from "../package-manager";

describe("subcommand implicit help ran on imcomplete command execution", () => {
let mockPackageManager: PackageManager;
describe("subcommand implicit help ran on incomplete command execution", () => {
runInTempDir();

beforeEach(() => {
mockPackageManager = {
cwd: process.cwd(),
// @ts-expect-error we're making a fake package manager here
type: "mockpm",
addDevDeps: jest.fn(),
install: jest.fn(),
};
(getPackageManager as jest.Mock).mockResolvedValue(mockPackageManager);
});

const std = mockConsoleMethods();
function endEventLoop() {
return new Promise((resolve) => setImmediate(resolve));
Expand All @@ -46,36 +30,28 @@ describe("subcommand implicit help ran on imcomplete command execution", () => {
🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
`);
});
});

describe("beta message for subcommands", () => {
const betaMsg =
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
const isWindows = process.platform === "win32";
it("should display for pages:dev", async () => {
let err: Error | undefined;
try {
execaSync("npx", ["wrangler", "pages", "dev"], {
shell: isWindows,
env: { BROWSER: "none", ...process.env },
}).stderr;
} catch (e: unknown) {
err = e as Error;
}
expect(err?.message.includes(betaMsg)).toBe(true);
});
describe("beta message for subcommands", () => {
it("should display for pages:dev", async () => {
await expect(
runWrangler("pages dev")
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Must specify a directory of static assets to serve or a command to run."`
);

expect(std.out).toMatchInlineSnapshot(
`"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"`
);
});

// Note that `wrangler pages functions` does nothing...

it("should display for pages:functions", async () => {
let err: Error | undefined;
try {
execaSync("npx", ["wrangler", "pages", "functions", "build"], {
shell: isWindows,
env: { BROWSER: "none", ...process.env },
});
} catch (e: unknown) {
err = e as Error;
}
it("should display for pages:functions:build", async () => {
await expect(runWrangler("pages functions build")).rejects.toThrowError();

expect(err?.message.includes(betaMsg)).toBe(true);
expect(std.out).toMatchInlineSnapshot(
`"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"`
);
});
});
});
8 changes: 4 additions & 4 deletions packages/wrangler/src/__tests__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe("wrangler route", () => {
it("shows a deprecation notice when `wrangler route` is run", async () => {
await expect(runWrangler("route")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler route\` has been deprecated.
Please use wrangler.toml and/or \`wrangler publish --routes\` to modify routes"
`);
Expand All @@ -18,7 +18,7 @@ describe("wrangler route", () => {
it("shows a deprecation notice when `wrangler route delete` is run", async () => {
await expect(runWrangler("route delete")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler route delete\` has been deprecated.
Remove the unwanted route(s) from wrangler.toml and run \`wrangler publish\` to remove your worker from those routes."
`);
Expand All @@ -27,7 +27,7 @@ describe("wrangler route", () => {
it("shows a deprecation notice when `wrangler route delete <id>` is run", async () => {
await expect(runWrangler("route delete some-zone-id")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler route delete\` has been deprecated.
Remove the unwanted route(s) from wrangler.toml and run \`wrangler publish\` to remove your worker from those routes."
`);
Expand All @@ -36,7 +36,7 @@ describe("wrangler route", () => {
it("shows a deprecation notice when `wrangler route list` is run", async () => {
await expect(runWrangler("route list")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"DEPRECATION WARNING:
"DEPRECATION:
\`wrangler route list\` has been deprecated.
Refer to wrangler.toml for a list of routes the worker will be deployed to upon publishing.
Refer to the Cloudflare Dashboard to see the routes this worker is currently running on."
Expand Down
6 changes: 4 additions & 2 deletions packages/wrangler/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import process from "process";
import { hideBin } from "yargs/helpers";
import { FatalError } from "./errors";
import { reportError, initReporting } from "./reporting";
import { main } from ".";

Expand All @@ -11,9 +12,10 @@ process.on("uncaughtExceptionMonitor", async (err, origin) => {
await reportError(err, origin);
});

main(hideBin(process.argv)).catch(() => {
main(hideBin(process.argv)).catch((e) => {
// The logging of any error that was thrown from `main()` is handled in the `yargs.fail()` handler.
// Here we just want to ensure that the process exits with a non-zero code.
// We don't want to do this inside the `main()` function, since that would kill the process when running our tests.
process.exit(1);
const exitCode = (e instanceof FatalError && e.code) || 1;
process.exit(exitCode);
});
11 changes: 11 additions & 0 deletions packages/wrangler/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class DeprecationError extends Error {
constructor(message: string) {
super(`DEPRECATION:\n${message}`);
}
}

export class FatalError extends Error {
constructor(message?: string, readonly code?: number) {
super(message);
}
}
6 changes: 1 addition & 5 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createWorkerUploadForm } from "./create-worker-upload-form";
import Dev from "./dev/dev";
import { confirm, prompt } from "./dialogs";
import { getEntry } from "./entry";
import { DeprecationError } from "./errors";
import {
getNamespaceId,
listNamespaces,
Expand Down Expand Up @@ -211,11 +212,6 @@ function demandOneOfOption(...options: string[]) {
}

class CommandLineArgsError extends Error {}
class DeprecationError extends Error {
constructor(message: string) {
super(`DEPRECATION WARNING:\n${message}`);
}
}

export async function main(argv: string[]): Promise<void> {
const wrangler = makeCLI(argv)
Expand Down
36 changes: 22 additions & 14 deletions packages/wrangler/src/pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getType } from "mime";
import { buildWorker } from "../pages/functions/buildWorker";
import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
import { writeRoutesModule } from "../pages/functions/routes";
import { FatalError } from "./errors";
import openInBrowser from "./open-in-browser";
import { toUrlPath } from "./paths";
import type { Config } from "../pages/functions/routes";
Expand All @@ -25,17 +26,20 @@ import type { BuilderCallback } from "yargs";
export const pagesBetaWarning =
"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";

const EXIT_CALLBACKS: (() => void)[] = [];
const EXIT = (message?: string, code?: number) => {
if (message) console.log(message);
if (code) process.exitCode = code;
EXIT_CALLBACKS.forEach((callback) => callback());
const CLEANUP_CALLBACKS: (() => void)[] = [];
const CLEANUP = () => {
CLEANUP_CALLBACKS.forEach((callback) => callback());
RUNNING_BUILDERS.forEach((builder) => builder.stop?.());
process.exit(code);
};

process.on("SIGINT", () => EXIT());
process.on("SIGTERM", () => EXIT());
process.on("SIGINT", () => {
CLEANUP();
process.exit();
});
process.on("SIGTERM", () => {
CLEANUP();
process.exit();
});

function isWindows() {
return process.platform === "win32";
Expand Down Expand Up @@ -108,11 +112,13 @@ async function spawnProxyProcess({
port?: number;
command: (string | number)[];
}): Promise<void | number> {
if (command.length === 0)
return EXIT(
if (command.length === 0) {
CLEANUP();
throw new FatalError(
"Must specify a directory of static assets to serve or a command to run.",
1
);
}

console.log(`Running ${command.join(" ")}...`);
const proxy = spawn(
Expand All @@ -126,7 +132,7 @@ async function spawnProxyProcess({
},
}
);
EXIT_CALLBACKS.push(() => {
CLEANUP_CALLBACKS.push(() => {
proxy.kill();
});

Expand Down Expand Up @@ -157,7 +163,8 @@ async function spawnProxyProcess({
.filter((port) => port !== undefined)[0];

if (port === undefined) {
return EXIT(
CLEANUP();
throw new FatalError(
"Could not automatically determine proxy port. Please specify the proxy port with --proxy.",
1
);
Expand Down Expand Up @@ -970,13 +977,14 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
});
}

EXIT_CALLBACKS.push(() => {
CLEANUP_CALLBACKS.push(() => {
server.close();
miniflare.dispose().catch((err) => miniflare.log.error(err));
});
} catch (e) {
miniflare.log.error(e as Error);
EXIT("Could not start Miniflare.", 1);
CLEANUP();
throw new FatalError("Could not start Miniflare.", 1);
}
}
)
Expand Down