diff --git a/.changeset/mighty-coins-cover.md b/.changeset/mighty-coins-cover.md new file mode 100644 index 000000000000..afdf50c4fd4a --- /dev/null +++ b/.changeset/mighty-coins-cover.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: mark R2 object and bucket not found errors as unreportable + +Previously, running `wrangler r2 objects {get,put}` with an object or bucket that didn't exist would ask if you wanted to report that error to Cloudflare. There's nothing we can do to fix this, so this change prevents the prompt in this case. diff --git a/.changeset/spotty-papayas-sneeze.md b/.changeset/spotty-papayas-sneeze.md new file mode 100644 index 000000000000..5cdd38ada03e --- /dev/null +++ b/.changeset/spotty-papayas-sneeze.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: ensure `wrangler dev --log-level` flag applied to all logs + +Previously, `wrangler dev` may have ignored the `--log-level` flag for some startup logs. This change ensures the `--log-level` flag is applied immediately. diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 34b38959b915..55d80f421a42 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,7 +13,7 @@ jobs: concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }}-${{ matrix.node }} cancel-in-progress: true - timeout-minutes: 30 + timeout-minutes: 15 if: github.repository_owner == 'cloudflare' && (github.event_name != 'pull_request' || (github.event_name == 'pull_request' && contains(github.event.*.labels.*.name, 'e2e' ))) name: "E2E Test" strategy: @@ -73,9 +73,20 @@ jobs: id: "find-wrangler" run: echo "dir=$(ls $HOME/wrangler-*.tgz)" >> $GITHUB_OUTPUT; - - name: Run tests - id: e2e-1 - continue-on-error: true + - name: Run tests (unix) + if: matrix.os == 'macos-13' || matrix.os == 'ubuntu-22.04' + run: | + pnpm add ${{ steps.find-wrangler.outputs.dir}} --global + pnpm run --filter wrangler test:e2e + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.TEST_CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.TEST_CLOUDFLARE_ACCOUNT_ID }} + WRANGLER: wrangler + NODE_OPTIONS: "--max_old_space_size=8192" + WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/ + + - name: Run tests (windows) + if: matrix.os == 'windows-2022' run: pnpm run --filter wrangler test:e2e env: CLOUDFLARE_API_TOKEN: ${{ secrets.TEST_CLOUDFLARE_API_TOKEN }} diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index e4caa9505c31..2c4c1065a6bc 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -1531,6 +1531,7 @@ export class Miniflare { // Get a `Fetcher` to that worker (NOTE: the `ProxyServer` Durable Object // shares its `env` with Miniflare's entry worker, so has access to routes) const bindingName = CoreBindings.SERVICE_USER_ROUTE_PREFIX + workerName; + const fetcher = proxyClient.env[bindingName]; if (fetcher === undefined) { // `#findAndAssertWorkerIndex()` will throw if a "worker" doesn't exist diff --git a/packages/miniflare/src/plugins/core/proxy/client.ts b/packages/miniflare/src/plugins/core/proxy/client.ts index 670d0a5b14b3..138285a2a5af 100644 --- a/packages/miniflare/src/plugins/core/proxy/client.ts +++ b/packages/miniflare/src/plugins/core/proxy/client.ts @@ -332,6 +332,7 @@ class ProxyStubHandler implements ProxyHandler { { value: stringifiedResult, unbufferedStream }, this.revivers ); + // We get an empty stack trace if we thread the caller through here, // specifying `this.#parseAsyncResponse` is good enough though, we just // get an extra `processTicksAndRejections` entry diff --git a/packages/miniflare/src/workers/core/devalue.ts b/packages/miniflare/src/workers/core/devalue.ts index 2b7306a704b6..bf6f7569447b 100644 --- a/packages/miniflare/src/workers/core/devalue.ts +++ b/packages/miniflare/src/workers/core/devalue.ts @@ -309,5 +309,6 @@ export function parseWithReadableStreams( }, ...revivers, }; + return parse(stringified.value, streamRevivers); } diff --git a/packages/wrangler/e2e/c3-integration.test.ts b/packages/wrangler/e2e/c3-integration.test.ts index a60dbe1d56c0..29fa335156cb 100644 --- a/packages/wrangler/e2e/c3-integration.test.ts +++ b/packages/wrangler/e2e/c3-integration.test.ts @@ -63,8 +63,7 @@ describe("c3 integration", () => { it("deploy the worker", async () => { const { stdout, stderr } = await runInWorker`$ ${WRANGLER} deploy`; expect(normalize(stdout)).toMatchInlineSnapshot(` - "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in. - Total Upload: xx KiB / gzip: xx KiB + "Total Upload: xx KiB / gzip: xx KiB Uploaded smoke-test-worker (TIMINGS) Published smoke-test-worker (TIMINGS) https://smoke-test-worker.SUBDOMAIN.workers.dev @@ -86,7 +85,7 @@ describe("c3 integration", () => { const { stdout, stderr } = await runInWorker`$$ ${WRANGLER} delete`; expect(normalize(stdout)).toMatchInlineSnapshot(` "? Are you sure you want to delete smoke-test-worker? This action cannot be undone. - 🤖 Using default value in non-interactive context: yes + 🤖 Using fallback value in non-interactive context: yes Successfully deleted smoke-test-worker" `); expect(stderr).toMatchInlineSnapshot('""'); diff --git a/packages/wrangler/e2e/deploy.test.ts b/packages/wrangler/e2e/deploy.test.ts deleted file mode 100644 index d6495139f62f..000000000000 --- a/packages/wrangler/e2e/deploy.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import crypto from "node:crypto"; -import path from "node:path"; -import shellac from "shellac"; -import { fetch } from "undici"; -import { beforeAll, describe, expect, it } from "vitest"; -import { CLOUDFLARE_ACCOUNT_ID } from "./helpers/account-id"; -import { normalizeOutput } from "./helpers/normalize"; -import { retry } from "./helpers/retry"; -import { dedent, makeRoot, seed } from "./helpers/setup"; -import { WRANGLER } from "./helpers/wrangler-command"; - -function matchWorkersDev(stdout: string): string { - return stdout.match( - /https:\/\/smoke-test-worker-.+?\.(.+?\.workers\.dev)/ - )?.[1] as string; -} - -describe("deploy", () => { - let workerName: string; - let workerPath: string; - let workersDev: string | null = null; - let runInRoot: typeof shellac; - let runInWorker: typeof shellac; - let normalize: (str: string) => string; - - beforeAll(async () => { - const root = await makeRoot(); - runInRoot = shellac.in(root).env(process.env); - workerName = `smoke-test-worker-${crypto.randomBytes(4).toString("hex")}`; - workerPath = path.join(root, workerName); - runInWorker = shellac.in(workerPath).env(process.env); - normalize = (str) => - normalizeOutput(str, { - [workerName]: "smoke-test-worker", - [CLOUDFLARE_ACCOUNT_ID]: "CLOUDFLARE_ACCOUNT_ID", - }); - }); - - it("init worker", async () => { - const { stdout } = - await runInRoot`$ ${WRANGLER} init --yes --no-delegate-c3 ${workerName}`; - - expect(normalize(stdout)).toContain( - "To publish your Worker to the Internet, run `npm run deploy`" - ); - }); - - it("deploy worker", async () => { - const { stdout } = await runInWorker`$ ${WRANGLER} deploy`; - expect(normalize(stdout)).toMatchInlineSnapshot(` - "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in. - Total Upload: xx KiB / gzip: xx KiB - Uploaded smoke-test-worker (TIMINGS) - Published smoke-test-worker (TIMINGS) - https://smoke-test-worker.SUBDOMAIN.workers.dev - Current Deployment ID: 00000000-0000-0000-0000-000000000000" - `); - workersDev = matchWorkersDev(stdout); - - const { text } = await retry( - (s) => s.status !== 200, - async () => { - const r = await fetch(`https://${workerName}.${workersDev}`); - return { text: await r.text(), status: r.status }; - } - ); - expect(text).toMatchInlineSnapshot('"Hello World!"'); - }); - - it("modify & deploy worker", async () => { - await seed(workerPath, { - "src/index.ts": dedent` - export default { - fetch(request) { - return new Response("Updated Worker!") - } - }`, - }); - const { stdout, stderr } = await runInWorker`$ ${WRANGLER} deploy`; - expect(normalize(stdout)).toMatchInlineSnapshot(` - "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in. - Total Upload: xx KiB / gzip: xx KiB - Uploaded smoke-test-worker (TIMINGS) - Published smoke-test-worker (TIMINGS) - https://smoke-test-worker.SUBDOMAIN.workers.dev - Current Deployment ID: 00000000-0000-0000-0000-000000000000" - `); - expect(stderr).toMatchInlineSnapshot('""'); - workersDev = matchWorkersDev(stdout); - - const { text } = await retry( - (s) => s.status !== 200 || s.text === "Hello World!", - async () => { - const r = await fetch(`https://${workerName}.${workersDev}`); - return { text: await r.text(), status: r.status }; - } - ); - expect(text).toMatchInlineSnapshot('"Updated Worker!"'); - }); - - it("delete worker", async () => { - const { stdout, stderr } = await runInWorker`$ ${WRANGLER} delete`; - expect(normalize(stdout)).toMatchInlineSnapshot(` - "? Are you sure you want to delete smoke-test-worker? This action cannot be undone. - 🤖 Using default value in non-interactive context: yes - Successfully deleted smoke-test-worker" - `); - expect(stderr).toMatchInlineSnapshot('""'); - const { status } = await retry( - (s) => s.status === 200 || s.status === 500, - async () => { - const r = await fetch(`https://${workerName}.${workersDev}`); - return { text: await r.text(), status: r.status }; - } - ); - expect(status).toBe(404); - }); -}); diff --git a/packages/wrangler/e2e/deployments.test.ts b/packages/wrangler/e2e/deployments.test.ts index 02bc9500120c..ff7697f5bfcb 100644 --- a/packages/wrangler/e2e/deployments.test.ts +++ b/packages/wrangler/e2e/deployments.test.ts @@ -56,8 +56,7 @@ describe("deployments", () => { it("deploy worker", async () => { const { stdout } = await runInWorker`$ ${WRANGLER} deploy`; expect(normalize(stdout)).toMatchInlineSnapshot(` - "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in. - Total Upload: xx KiB / gzip: xx KiB + "Total Upload: xx KiB / gzip: xx KiB Uploaded smoke-test-worker (TIMINGS) Published smoke-test-worker (TIMINGS) https://smoke-test-worker.SUBDOMAIN.workers.dev @@ -101,8 +100,7 @@ describe("deployments", () => { }); const { stdout, stderr } = await runInWorker`$ ${WRANGLER} deploy`; expect(normalize(stdout)).toMatchInlineSnapshot(` - "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in. - Total Upload: xx KiB / gzip: xx KiB + "Total Upload: xx KiB / gzip: xx KiB Uploaded smoke-test-worker (TIMINGS) Published smoke-test-worker (TIMINGS) https://smoke-test-worker.SUBDOMAIN.workers.dev @@ -178,7 +176,7 @@ describe("deployments", () => { const { stdout, stderr } = await runInWorker`$ ${WRANGLER} delete`; expect(normalize(stdout)).toMatchInlineSnapshot(` "? Are you sure you want to delete smoke-test-worker? This action cannot be undone. - 🤖 Using default value in non-interactive context: yes + 🤖 Using fallback value in non-interactive context: yes Successfully deleted smoke-test-worker" `); expect(stderr).toMatchInlineSnapshot('""'); diff --git a/packages/wrangler/e2e/dev.test.ts b/packages/wrangler/e2e/dev.test.ts index ee2782d5fbc9..faeb94c0e6a2 100644 --- a/packages/wrangler/e2e/dev.test.ts +++ b/packages/wrangler/e2e/dev.test.ts @@ -4,15 +4,23 @@ import { existsSync } from "node:fs"; import * as nodeNet from "node:net"; import path from "node:path"; import { setTimeout } from "node:timers/promises"; -import getPort from "get-port"; import shellac from "shellac"; -import { fetch } from "undici"; +import { Agent, fetch, setGlobalDispatcher } from "undici"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { normalizeOutput } from "./helpers/normalize"; import { retry } from "./helpers/retry"; import { dedent, makeRoot, seed } from "./helpers/setup"; import { WRANGLER } from "./helpers/wrangler-command"; +// Use `Agent` with lower timeouts so `fetch()`s inside `retry()`s don't block for a long time +setGlobalDispatcher( + new Agent({ + connectTimeout: 10_000, + headersTimeout: 10_000, + bodyTimeout: 10_000, + }) +); + type MaybePromise = T | Promise; const waitForPortToBeBound = async (port: number) => { @@ -31,11 +39,7 @@ const waitUntilOutputContains = async ( (stdout) => !stdout.includes(substring), async () => { await setTimeout(intervalMs); - return ( - normalizeOutput(session.stdout) + - "\n\n\n" + - normalizeOutput(session.stderr) - ); + return session.stdout + "\n\n\n" + session.stderr; } ); }; @@ -46,6 +50,20 @@ interface SessionData { stderr: string; } +function getPort() { + return new Promise((resolve, reject) => { + const server = nodeNet.createServer((socket) => socket.destroy()); + server.listen(0, () => { + const address = server.address(); + assert(typeof address === "object" && address !== null); + server.close((err) => { + if (err) reject(err); + else resolve(address.port); + }); + }); + }); +} + async function runDevSession( workerPath: string, flags: string, @@ -65,7 +83,11 @@ async function runDevSession( // Must use the `in` statement in the shellac script rather than `.in()` modifier on the `shellac` object // otherwise the working directory does not get picked up. + let promiseResolve: (() => void) | undefined; + const promise = new Promise((resolve) => (promiseResolve = resolve)); const bg = await shellac.env(process.env).bg` + await ${() => promise} + in ${workerPath} { exits { $ ${WRANGLER} dev ${flags} @@ -82,12 +104,18 @@ async function runDevSession( }; bg.process.stdout.on("data", (chunk) => (sessionData.stdout += chunk)); bg.process.stderr.on("data", (chunk) => (sessionData.stderr += chunk)); + // Only start `wrangler dev` once we've registered output listeners so we don't miss messages + promiseResolve?.(); await session(sessionData); return bg.promise; } finally { - if (pid) process.kill(pid); + try { + if (pid) process.kill(pid); + } catch { + // Ignore errors if we failed to kill the process (i.e. ESRCH if it's already terminated) + } } } @@ -489,14 +517,14 @@ describe("writes debug logs to hidden file", () => { async (session) => { await waitForPortToBeBound(session.port); - await waitUntilOutputContains(session, "🐛 Writing debug logs to"); + await waitUntilOutputContains(session, "Writing logs to"); await setTimeout(1000); // wait a bit to ensure the file is written to disk } ); const filepath = finalA.stdout.match( - /🐛 Writing debug logs to "(.+\.log)"/ + /🪵 {2}Writing logs to "(.+\.log)"/ )?.[1]; assert(filepath); @@ -511,13 +539,13 @@ describe("writes debug logs to hidden file", () => { }); const filepath = finalA.stdout.match( - /🐛 Writing debug logs to "(.+\.log)"/ + /🪵 {2}Writing logs to "(.+\.log)"/ )?.[1]; expect(filepath).toBeUndefined(); }); - it("rewrites address-in-use error logs", async () => { + it.skip("rewrites address-in-use error logs", async () => { // 1. start worker A on a (any) port await a.runDevSession("", async (sessionA) => { const normalize = (text: string) => diff --git a/packages/wrangler/e2e/helpers/normalize.ts b/packages/wrangler/e2e/helpers/normalize.ts index f5911fd7afdb..b3582e827437 100644 --- a/packages/wrangler/e2e/helpers/normalize.ts +++ b/packages/wrangler/e2e/helpers/normalize.ts @@ -6,6 +6,7 @@ export function normalizeOutput( ): string { const functions = [ removeVersionHeader, + removeStandardPricingWarning, npmStripTimings, removeWorkersDev, removeUUID, @@ -139,10 +140,12 @@ export function normalizeTempDirs(stdout: string): string { * Debug log files are created with a timestamp, so we replace the debug log filepath timestamp with */ export function normalizeDebugLogFilepath(stdout: string): string { - return stdout.replace( - /(🐛 Writing debug logs to ".+wrangler-debug)-.+\.log/, - "$1-.log" - ); + return stdout + .replace(/🪵 {2}Writing logs to ".+\.log"/, '🪵 Writing logs to ""') + .replace( + /🪵 {2}Logs were written to ".+\.log"/, + '🪵 Logs were written to ""' + ); } /** @@ -154,3 +157,13 @@ export function squashLocalNetworkBindings(stdout: string): string { "[mf:inf] Ready on http://:\n[mf:inf] - http://:" ); } + +/** + * This may or may not be displayed depending on whether the test account has accepted standard pricing. + */ +function removeStandardPricingWarning(stdout: string): string { + return stdout.replace( + "🚧 New Workers Standard pricing is now available. Please visit the dashboard to view details and opt-in to new pricing: https://dash.cloudflare.com/CLOUDFLARE_ACCOUNT_ID/workers/standard/opt-in.", + "" + ); +} diff --git a/packages/wrangler/e2e/r2.test.ts b/packages/wrangler/e2e/r2.test.ts index 44708b7ec760..ce863b0be307 100644 --- a/packages/wrangler/e2e/r2.test.ts +++ b/packages/wrangler/e2e/r2.test.ts @@ -83,8 +83,8 @@ describe("r2", () => { If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" `); expect(normalize(stderr)).toMatchInlineSnapshot(` - "X [ERROR] Failed to fetch /accounts/CLOUDFLARE_ACCOUNT_ID/r2/buckets/wrangler-smoke-test-bucket/objects/testr2 - 404: Not Found); - " + "X [ERROR] The specified key does not exist. + 🪵 Logs were written to \\"\\"" `); }); @@ -112,8 +112,8 @@ describe("r2", () => { If you think this is a bug then please create an issue at https://github.com/cloudflare/workers-sdk/issues/new/choose" `); expect(normalize(stderr)).toMatchInlineSnapshot(` - "X [ERROR] Failed to fetch /accounts/CLOUDFLARE_ACCOUNT_ID/r2/buckets/wrangler-smoke-test-bucket/objects/testr2 - 404: Not Found); - " + "X [ERROR] The specified bucket does not exist. + 🪵 Logs were written to \\"\\"" `); }); }); diff --git a/packages/wrangler/src/cfetch/internal.ts b/packages/wrangler/src/cfetch/internal.ts index 796c99251763..cac73ce511a3 100644 --- a/packages/wrangler/src/cfetch/internal.ts +++ b/packages/wrangler/src/cfetch/internal.ts @@ -196,7 +196,7 @@ type ResponseWithBody = Response & { body: NonNullable }; export async function fetchR2Objects( resource: string, bodyInit: RequestInit = {} -): Promise { +): Promise { await requireLoggedIn(); const auth = requireApiToken(); const headers = cloneHeaders(bodyInit.headers); @@ -210,6 +210,8 @@ export async function fetchR2Objects( if (response.ok && response.body) { return response as ResponseWithBody; + } else if (response.status === 404) { + return null; } else { throw new Error( `Failed to fetch ${resource} - ${response.status}: ${response.statusText});` diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index c0bd22261619..62d41c73726f 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -712,13 +712,13 @@ async function validateDevServerSettings( const initialIp = args.ip || config.dev.ip; const initialIpListenCheck = initialIp === "*" ? "0.0.0.0" : initialIp; const getLocalPort = memoizeGetPort(DEFAULT_LOCAL_PORT, initialIpListenCheck); - const getInspectorPort = memoizeGetPort(DEFAULT_INSPECTOR_PORT, "localhost"); + const getInspectorPort = memoizeGetPort(DEFAULT_INSPECTOR_PORT, "127.0.0.1"); // Our inspector proxy server will be binding to the result of // `getInspectorPort`. If we attempted to bind workerd to the same inspector // port, we'd get a port already in use error. Therefore, generate a new port // for our runtime to bind its inspector service to. - const getRuntimeInspectorPort = memoizeGetPort(0, "localhost"); + const getRuntimeInspectorPort = memoizeGetPort(0, "127.0.0.1"); if (config.services && config.services.length > 0) { logger.warn( diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 8abe7c16c4ff..147463d0697b 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -40,7 +40,7 @@ import { generateHandler, generateOptions } from "./generate"; import { hyperdrive } from "./hyperdrive/index"; import { initHandler, initOptions } from "./init"; import { kvBulk, kvKey, kvNamespace } from "./kv"; -import { logBuildFailure, logger } from "./logger"; +import { logBuildFailure, logger, LOGGER_LEVELS } from "./logger"; import * as metrics from "./metrics"; import { mTlsCertificateCommands } from "./mtls-certificate/cli"; import { pages } from "./pages"; @@ -70,6 +70,7 @@ import { versionsUploadHandler, versionsUploadOptions } from "./versions"; import { whoami } from "./whoami"; import { asJson } from "./yargs-types"; import type { Config } from "./config"; +import type { LoggerLevel } from "./logger"; import type { CommonYargsArgv, CommonYargsOptions } from "./yargs-types"; import type { Arguments, CommandModule } from "yargs"; @@ -223,6 +224,11 @@ export function createCLIParser(argv: string[]) { hidden: true, }) .check((args) => { + // Update logger level, before we do any logging + if (Object.keys(LOGGER_LEVELS).includes(args.logLevel as string)) { + logger.loggerLevel = args.logLevel as LoggerLevel; + } + // Grab locally specified env params from `.env` file const loaded = loadDotEnv(".env", args.env); for (const [key, value] of Object.entries(loaded?.parsed ?? {})) { diff --git a/packages/wrangler/src/r2/helpers.ts b/packages/wrangler/src/r2/helpers.ts index 5acee6e6bafb..8c20bb6c6638 100644 --- a/packages/wrangler/src/r2/helpers.ts +++ b/packages/wrangler/src/r2/helpers.ts @@ -97,7 +97,7 @@ export async function getR2Object( bucketName: string, objectName: string, jurisdiction?: string -): Promise { +): Promise { const headers: HeadersInit = {}; if (jurisdiction !== undefined) { headers["cf-r2-jurisdiction"] = jurisdiction; @@ -110,7 +110,7 @@ export async function getR2Object( } ); - return response.body; + return response === null ? null : response.body; } /** @@ -142,7 +142,7 @@ export async function putR2Object( headers["cf-r2-jurisdiction"] = jurisdiction; } - await fetchR2Objects( + const result = await fetchR2Objects( `/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`, { body: object, @@ -151,6 +151,9 @@ export async function putR2Object( duplex: "half", } ); + if (result === null) { + throw new UserError("The specified bucket does not exist."); + } } /** * Delete an Object diff --git a/packages/wrangler/src/r2/index.ts b/packages/wrangler/src/r2/index.ts index 3598d210a936..c788c8904ac3 100644 --- a/packages/wrangler/src/r2/index.ts +++ b/packages/wrangler/src/r2/index.ts @@ -138,6 +138,9 @@ export function r2(r2Yargs: CommonYargsArgv) { key, jurisdiction ); + if (input === null) { + throw new UserError("The specified key does not exist."); + } await stream.promises.pipeline(input, output); } if (!pipe) logger.log("Download complete."); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6bd9610d0b07..ee7047c6b4ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -805,7 +805,7 @@ importers: version: 8.49.0 eslint-config-turbo: specifier: latest - version: 1.10.15(eslint@8.49.0) + version: 1.12.2(eslint@8.49.0) eslint-plugin-import: specifier: 2.26.x version: 2.26.0(@typescript-eslint/parser@6.7.2)(eslint@8.49.0) @@ -10703,13 +10703,13 @@ packages: eslint: 8.49.0 dev: true - /eslint-config-turbo@1.10.15(eslint@8.49.0): - resolution: {integrity: sha512-76mpx2x818JZE26euen14utYcFDxOahZ9NaWA+6Xa4pY2ezVKVschuOxS96EQz3o3ZRSmcgBOapw/gHbN+EKxQ==} + /eslint-config-turbo@1.12.2(eslint@8.49.0): + resolution: {integrity: sha512-JHTGtDQuISBEWIorHenu5AeX1nv16NiDgDVRi1i0VyeYw0SiVh+lSQbv4BawXSnG1nOFpjbopAQdZvdB3PwXbQ==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.49.0 - eslint-plugin-turbo: 1.10.15(eslint@8.49.0) + eslint-plugin-turbo: 1.12.2(eslint@8.49.0) dev: false /eslint-import-resolver-node@0.3.7: @@ -11136,8 +11136,8 @@ packages: - typescript dev: true - /eslint-plugin-turbo@1.10.15(eslint@8.49.0): - resolution: {integrity: sha512-Tv4QSKV/U56qGcTqS/UgOvb9HcKFmWOQcVh3HEaj7of94lfaENgfrtK48E2CckQf7amhKs1i+imhCsNCKjkQyA==} + /eslint-plugin-turbo@1.12.2(eslint@8.49.0): + resolution: {integrity: sha512-/l0aGvZRzK1LMRTibRd6ZbEEuD5TtGotDTkZpxSIWA1FI764pWVvQduQMKBaRuz7aTuAo0WxatD8v1scK+qRWw==} peerDependencies: eslint: '>6.6.0' dependencies: