diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index 7490024f4761..0fa1b30e37b7 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -1030,11 +1030,12 @@ describe("normalizeAndValidateConfig()", () => { - \\"unsafe\\" fields are experimental and may change or break at any time. - In wrangler.toml, you have configured [durable_objects] exported by this Worker (CLASS1), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml: - \`\`\` - [[migrations]] - tag = \\"v1\\" # Should be unique for each entry - new_classes = [\\"CLASS1\\"] - \`\`\` + \`\`\` + [[migrations]] + tag = \\"v1\\" + new_classes = [ \\"CLASS1\\" ] + + \`\`\` Refer to https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more details." `); diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 2d96ce3dcef1..99ca8b4ebd7a 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -5324,13 +5324,13 @@ addEventListener('fetch', event => {});` } expect(err?.message.replaceAll(/\d/g, "X")).toMatchInlineSnapshot(` - "A compatibility_date is required when publishing. Add the following to your wrangler.toml file:. - \`\`\` - compatibility_date = \\"XXXX-XX-XX\\" - \`\`\` - Or you could pass it in your terminal as \`--compatibility-date XXXX-XX-XX\` - See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." - `); + "A compatibility_date is required when publishing. Add the following to your wrangler.toml file:. + \`\`\` + {\\"compatibility_date\\":\\"XXXX-XX-XX\\"} + \`\`\` + Or you could pass it in your terminal as \`--compatibility-date XXXX-XX-XX\` + See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." + `); }); it("should error if a compatibility_date is missing and suggest the correct month", async () => { @@ -5347,13 +5347,13 @@ addEventListener('fetch', event => {});` } expect(err?.message).toMatchInlineSnapshot(` - "A compatibility_date is required when publishing. Add the following to your wrangler.toml file:. - \`\`\` - compatibility_date = \\"2020-12-01\\" - \`\`\` - Or you could pass it in your terminal as \`--compatibility-date 2020-12-01\` - See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." - `); + "A compatibility_date is required when publishing. Add the following to your wrangler.toml file:. + \`\`\` + {\\"compatibility_date\\":\\"2020-12-01\\"} + \`\`\` + Or you could pass it in your terminal as \`--compatibility-date 2020-12-01\` + See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information." + `); }); it("should enable the workers.dev domain if workers_dev is undefined and subdomain is not already available", async () => { @@ -6073,24 +6073,25 @@ addEventListener('fetch', event => {});` `); expect(std.err).toMatchInlineSnapshot(`""`); expect(std.warn).toMatchInlineSnapshot(` - "▲ [WARNING] Processing wrangler.toml configuration: + "▲ [WARNING] Processing wrangler.toml configuration: - - In wrangler.toml, you have configured [durable_objects] exported by this Worker (SomeClass), - but no [migrations] for them. This may not work as expected until you add a [migrations] section - to your wrangler.toml. Add this configuration to your wrangler.toml: + - In wrangler.toml, you have configured [durable_objects] exported by this Worker (SomeClass), + but no [migrations] for them. This may not work as expected until you add a [migrations] section + to your wrangler.toml. Add this configuration to your wrangler.toml: - \`\`\` - [[migrations]] - tag = \\"v1\\" # Should be unique for each entry - new_classes = [\\"SomeClass\\"] - \`\`\` + \`\`\` + [[migrations]] + tag = \\"v1\\" + new_classes = [ \\"SomeClass\\" ] - Refer to - https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more - details. + \`\`\` - " - `); + Refer to + https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more + details. + + " + `); }); it("does not warn if all the durable object bindings are to external classes", async () => { diff --git a/packages/wrangler/src/__tests__/dev.test.ts b/packages/wrangler/src/__tests__/dev.test.ts index d354249aca84..afbbeef96927 100644 --- a/packages/wrangler/src/__tests__/dev.test.ts +++ b/packages/wrangler/src/__tests__/dev.test.ts @@ -1241,11 +1241,12 @@ describe.sequential("wrangler dev", () => { CLASS_3), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml: - \`\`\` - [[migrations]] - tag = \\"v1\\" # Should be unique for each entry - new_classes = [\\"CLASS_1\\", \\"CLASS_3\\"] - \`\`\` + \`\`\` + [[migrations]] + tag = \\"v1\\" + new_classes = [ \\"CLASS_1\\", \\"CLASS_3\\" ] + + \`\`\` Refer to https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more diff --git a/packages/wrangler/src/__tests__/kv.test.ts b/packages/wrangler/src/__tests__/kv.test.ts index 9b62c749168f..acaf5b9492d8 100644 --- a/packages/wrangler/src/__tests__/kv.test.ts +++ b/packages/wrangler/src/__tests__/kv.test.ts @@ -1,5 +1,6 @@ import { writeFileSync } from "node:fs"; import { http, HttpResponse } from "msw"; +import dedent from "ts-dedent"; import { endEventLoop } from "./helpers/end-event-loop"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; import { mockConsoleMethods } from "./helpers/mock-console"; @@ -9,6 +10,10 @@ import { mockProcess } from "./helpers/mock-process"; import { msw } from "./helpers/msw"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; +import { + writeWranglerJson, + writeWranglerToml, +} from "./helpers/write-wrangler-toml"; import type { KeyValue, KVNamespaceInfo, @@ -190,60 +195,109 @@ describe("wrangler", () => { `); }); - it("should create a namespace", async () => { - mockCreateRequest("worker-UnitTestNamespace"); - await runWrangler("kv namespace create UnitTestNamespace"); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating namespace with title \\"worker-UnitTestNamespace\\" - ✨ Success! - Add the following to your configuration file in your kv_namespaces array: - [[kv_namespaces]] - binding = \\"UnitTestNamespace\\" - id = \\"some-namespace-id\\"" - `); - }); - - it("should create a preview namespace if configured to do so", async () => { - mockCreateRequest("worker-UnitTestNamespace_preview"); - await runWrangler("kv namespace create UnitTestNamespace --preview"); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating namespace with title \\"worker-UnitTestNamespace_preview\\" - ✨ Success! - Add the following to your configuration file in your kv_namespaces array: - [[kv_namespaces]] - binding = \\"UnitTestNamespace\\" - preview_id = \\"some-namespace-id\\"" - `); - }); + it.each(["toml", "jsonc"])( + "should create a namespace", + async (format) => { + format === "toml" + ? writeWranglerToml({ name: "worker" }) + : writeWranglerJson({ name: "worker" }); - it("should create a namespace using configured worker name", async () => { - writeFileSync("./wrangler.toml", 'name = "other-worker"', "utf-8"); - mockCreateRequest("other-worker-UnitTestNamespace"); - await runWrangler("kv namespace create UnitTestNamespace"); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating namespace with title \\"other-worker-UnitTestNamespace\\" - ✨ Success! - Add the following to your configuration file in your kv_namespaces array: - [[kv_namespaces]] - binding = \\"UnitTestNamespace\\" - id = \\"some-namespace-id\\"" - `); - }); + mockCreateRequest("worker-UnitTestNamespace"); + await runWrangler("kv namespace create UnitTestNamespace"); + expect(std.out).toContain(dedent` + 🌀 Creating namespace with title "worker-UnitTestNamespace" + ✨ Success! + Add the following to your configuration file in your kv_namespaces array: + `); + if (format === "toml") { + expect(std.out).toContain("[[kv_namespaces]]"); + } else { + expect(std.out).toContain(`"kv_namespaces": [`); + } + } + ); + + it.each(["toml", "jsonc"])( + "should create a preview namespace if configured to do so", + async (format) => { + format === "toml" + ? writeWranglerToml({ name: "worker" }) + : writeWranglerJson({ name: "worker" }); + + mockCreateRequest("worker-UnitTestNamespace_preview"); + await runWrangler("kv namespace create UnitTestNamespace --preview"); + expect(std.out).toContain(dedent` + 🌀 Creating namespace with title "worker-UnitTestNamespace_preview" + ✨ Success! + Add the following to your configuration file in your kv_namespaces array: + `); + if (format === "toml") { + expect(std.out).toContain("[[kv_namespaces]]"); + } else { + expect(std.out).toContain(`"kv_namespaces": [`); + } + } + ); + + it.each(["toml", "jsonc"])( + "should create a namespace using configured worker name", + async (format) => { + format === "toml" + ? writeWranglerToml({ name: "other-worker" }) + : writeWranglerJson({ name: "other-worker" }); + + mockCreateRequest("other-worker-UnitTestNamespace"); + await runWrangler("kv namespace create UnitTestNamespace"); + expect(std.out).toContain(dedent` + 🌀 Creating namespace with title "other-worker-UnitTestNamespace" + ✨ Success! + Add the following to your configuration file in your kv_namespaces array: + `); + if (format === "toml") { + expect(std.out).toContain("[[kv_namespaces]]"); + } else { + expect(std.out).toContain(`"kv_namespaces": [`); + } + } + ); + + it.each(["jsonc", "toml"])( + "should create a namespace in an environment if configured to do so", + async (format) => { + format === "toml" + ? writeWranglerToml({ + name: "worker", + env: { + customEnv: { + name: "worker", + }, + }, + }) + : writeWranglerJson({ + name: "worker", + env: { + customEnv: { + name: "worker", + }, + }, + }); - it("should create a namespace in an environment if configured to do so", async () => { - mockCreateRequest("worker-customEnv-UnitTestNamespace"); - await runWrangler( - "kv namespace create UnitTestNamespace --env customEnv" - ); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating namespace with title \\"worker-customEnv-UnitTestNamespace\\" - ✨ Success! - Add the following to your configuration file in your kv_namespaces array under [env.customEnv]: - [[kv_namespaces]] - binding = \\"UnitTestNamespace\\" - id = \\"some-namespace-id\\"" - `); - }); + mockCreateRequest("worker-customEnv-UnitTestNamespace"); + await runWrangler( + "kv namespace create UnitTestNamespace --env customEnv" + ); + expect(std.out).toContain(dedent` + 🌀 Creating namespace with title "worker-customEnv-UnitTestNamespace" + ✨ Success! + Add the following to your configuration file in your kv_namespaces array under [env.customEnv]: + `); + if (format === "toml") { + expect(std.out).toContain("[[kv_namespaces]]"); + } else { + expect(std.out).toContain(`"kv_namespaces": [`); + } + } + ); }); describe("list", () => { diff --git a/packages/wrangler/src/__tests__/queues.test.ts b/packages/wrangler/src/__tests__/queues.test.ts index 06ee4189a967..1c36d4943d5e 100644 --- a/packages/wrangler/src/__tests__/queues.test.ts +++ b/packages/wrangler/src/__tests__/queues.test.ts @@ -4,6 +4,10 @@ import { mockConsoleMethods } from "./helpers/mock-console"; import { msw } from "./helpers/msw"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; +import { + writeWranglerJson, + writeWranglerToml, +} from "./helpers/write-wrangler-toml"; import type { PostTypedConsumerBody, QueueResponse } from "../queues/client"; describe("wrangler", () => { @@ -254,24 +258,15 @@ describe("wrangler", () => { `); }); - it("should create a queue", async () => { + it.each(["toml", "jsonc"])("should create a queue", async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); const requests = mockCreateRequest("testQueue"); await runWrangler("queues create testQueue"); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating queue 'testQueue' - ✅ Created queue 'testQueue' - - Configure your Worker to send messages to this queue: - - [[queues.producers]] - queue = \\"testQueue\\" - binding = \\"testQueue\\" - - Configure your Worker to consume messages from this queue: - - [[queues.consumers]] - queue = \\"testQueue\\"" - `); + if (format === "toml") { + expect(std.out).toContain("[[queues.producers]]"); + } else { + expect(std.out).toContain(`"queues": {`); + } expect(requests.count).toEqual(1); }); @@ -315,26 +310,25 @@ describe("wrangler", () => { `); }); - it("should send queue settings with delivery delay", async () => { - const requests = mockCreateRequest("testQueue", { delivery_delay: 10 }); - await runWrangler("queues create testQueue --delivery-delay-secs=10"); - expect(std.out).toMatchInlineSnapshot(` - "🌀 Creating queue 'testQueue' - ✅ Created queue 'testQueue' - - Configure your Worker to send messages to this queue: + it.each(["toml", "jsonc"])( + "should send queue settings with delivery delay", + async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); - [[queues.producers]] - queue = \\"testQueue\\" - binding = \\"testQueue\\" + const requests = mockCreateRequest("testQueue", { + delivery_delay: 10, + }); + await runWrangler("queues create testQueue --delivery-delay-secs=10"); - Configure your Worker to consume messages from this queue: + if (format === "toml") { + expect(std.out).toContain("[[queues.producers]]"); + } else { + expect(std.out).toContain(`"queues": {`); + } - [[queues.consumers]] - queue = \\"testQueue\\"" - `); - expect(requests.count).toEqual(1); - }); + expect(requests.count).toEqual(1); + } + ); it("should show an error when two delivery delays are set", async () => { const requests = mockCreateRequest("testQueue", { delivery_delay: 0 }); diff --git a/packages/wrangler/src/__tests__/r2.test.ts b/packages/wrangler/src/__tests__/r2.test.ts index 604df2722a13..a9b362cc7de5 100644 --- a/packages/wrangler/src/__tests__/r2.test.ts +++ b/packages/wrangler/src/__tests__/r2.test.ts @@ -10,6 +10,10 @@ import { useMockIsTTY } from "./helpers/mock-istty"; import { createFetchResult, msw, mswR2handlers } from "./helpers/msw"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; +import { + writeWranglerJson, + writeWranglerToml, +} from "./helpers/write-wrangler-toml"; import type { PutNotificationRequestBody, R2BucketInfo, @@ -254,72 +258,84 @@ describe("r2", () => { `); }); - it("should create a bucket & check request inputs", async () => { - msw.use( - http.post( - "*/accounts/:accountId/r2/buckets", - async ({ request, params }) => { - const { accountId } = params; - expect(accountId).toEqual("some-account-id"); - expect(await request.json()).toEqual({ name: "testBucket" }); - return HttpResponse.json(createFetchResult({})); - }, - { once: true } - ) - ); - await runWrangler("r2 bucket create testBucket"); - expect(std.out).toMatchInlineSnapshot(` -"Creating bucket 'testBucket'... + it.each(["toml", "jsonc"])( + "should create a bucket & check request inputs", + async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); + + msw.use( + http.post( + "*/accounts/:accountId/r2/buckets", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(await request.json()).toEqual({ name: "testBucket" }); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ) + ); + await runWrangler("r2 bucket create testBucket"); + expect(std.out).toContain(`Creating bucket 'testBucket'... ✅ Created bucket 'testBucket' with default storage class of Standard. -Configure your Worker to write objects to this bucket: +Configure your Worker to write objects to this bucket:`); + if (format === "toml") { + expect(std.out).toContain("[[r2_buckets]]"); + } else { + expect(std.out).toContain(`"r2_buckets": [`); + } + } + ); -[[r2_buckets]] -bucket_name = \\"testBucket\\" -binding = \\"testBucket\\"" - `); - }); + it.each(["toml", "jsonc"])( + "should create a bucket with the expected jurisdiction", + async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); - it("should create a bucket with the expected jurisdiction", async () => { - msw.use( - http.post( - "*/accounts/:accountId/r2/buckets", - async ({ request, params }) => { - const { accountId } = params; - expect(accountId).toEqual("some-account-id"); - expect(request.headers.get("cf-r2-jurisdiction")).toEqual("eu"); - expect(await request.json()).toEqual({ name: "testBucket" }); - return HttpResponse.json(createFetchResult({})); - }, - { once: true } - ) - ); - await runWrangler("r2 bucket create testBucket -J eu"); - expect(std.out).toMatchInlineSnapshot(` -"Creating bucket 'testBucket (eu)'... + msw.use( + http.post( + "*/accounts/:accountId/r2/buckets", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(request.headers.get("cf-r2-jurisdiction")).toEqual("eu"); + expect(await request.json()).toEqual({ name: "testBucket" }); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ) + ); + await runWrangler("r2 bucket create testBucket -J eu"); + expect(std.out).toContain(`Creating bucket 'testBucket (eu)'... ✅ Created bucket 'testBucket (eu)' with default storage class of Standard. -Configure your Worker to write objects to this bucket: +Configure your Worker to write objects to this bucket:`); + if (format === "toml") { + expect(std.out).toContain("[[r2_buckets]]"); + } else { + expect(std.out).toContain(`"r2_buckets": [`); + } + } + ); -[[r2_buckets]] -bucket_name = \\"testBucket\\" -binding = \\"testBucket\\"" - `); - }); + it.each(["toml", "jsonc"])( + "should create a bucket with the expected default storage class", + async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); - it("should create a bucket with the expected default storage class", async () => { - await runWrangler("r2 bucket create testBucket -s InfrequentAccess"); - expect(std.out).toMatchInlineSnapshot(` -"Creating bucket 'testBucket'... + await runWrangler("r2 bucket create testBucket -s InfrequentAccess"); + expect(std.out).toContain(`Creating bucket 'testBucket'... ✅ Created bucket 'testBucket' with default storage class of InfrequentAccess. -Configure your Worker to write objects to this bucket: - -[[r2_buckets]] -bucket_name = \\"testBucket\\" -binding = \\"testBucket\\"" - `); - }); +Configure your Worker to write objects to this bucket:`); + if (format === "toml") { + expect(std.out).toContain("[[r2_buckets]]"); + } else { + expect(std.out).toContain(`"r2_buckets": [`); + } + } + ); it("should error if storage class is invalid", async () => { await expect( @@ -340,34 +356,37 @@ binding = \\"testBucket\\"" " `); }); - it("should create a bucket with the expected location hint", async () => { - msw.use( - http.post( - "*/accounts/:accountId/r2/buckets", - async ({ request, params }) => { - const { accountId } = params; - expect(accountId).toEqual("some-account-id"); - expect(await request.json()).toEqual({ - name: "testBucket", - locationHint: "weur", - }); - return HttpResponse.json(createFetchResult({})); - }, - { once: true } - ) - ); - await runWrangler("r2 bucket create testBucket --location weur"); - expect(std.out).toMatchInlineSnapshot(` -"Creating bucket 'testBucket'... + it.each(["toml", "jsonc"])( + "should create a bucket with the expected location hint", + async (format) => { + format === "toml" ? writeWranglerToml() : writeWranglerJson(); + msw.use( + http.post( + "*/accounts/:accountId/r2/buckets", + async ({ request, params }) => { + const { accountId } = params; + expect(accountId).toEqual("some-account-id"); + expect(await request.json()).toEqual({ + name: "testBucket", + locationHint: "weur", + }); + return HttpResponse.json(createFetchResult({})); + }, + { once: true } + ) + ); + await runWrangler("r2 bucket create testBucket --location weur"); + expect(std.out).toContain(`Creating bucket 'testBucket'... ✅ Created bucket 'testBucket' with location hint weur and default storage class of Standard. -Configure your Worker to write objects to this bucket: - -[[r2_buckets]] -bucket_name = \\"testBucket\\" -binding = \\"testBucket\\"" - `); - }); +Configure your Worker to write objects to this bucket:`); + if (format === "toml") { + expect(std.out).toContain("[[r2_buckets]]"); + } else { + expect(std.out).toContain(`"r2_buckets": [`); + } + } + ); }); describe("update", () => { diff --git a/packages/wrangler/src/config/config.ts b/packages/wrangler/src/config/config.ts index a09d278a4faa..aa7718d0a780 100644 --- a/packages/wrangler/src/config/config.ts +++ b/packages/wrangler/src/config/config.ts @@ -22,7 +22,9 @@ import type { CamelCaseKey } from "yargs"; * - `@breaking`: the deprecation/optionality is a breaking change from Wrangler v1. * - `@todo`: there's more work to be done (with details attached). */ -export type Config = ConfigFields & PagesConfigFields & Environment; +export type Config = ConfigFields & + PagesConfigFields & + Environment & { parsedFormat?: "jsonc" | "toml" }; export type RawConfig = Partial> & PagesConfigFields & diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index a6ceaf625093..7c5ee5733b63 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -1,4 +1,5 @@ import fs from "node:fs"; +import TOML from "@iarna/toml"; import chalk from "chalk"; import dotenv from "dotenv"; import { findUpSync } from "find-up"; @@ -7,6 +8,7 @@ import { getFlag } from "../experimental-flags"; import { logger } from "../logger"; import { EXIT_CODE_INVALID_PAGES_CONFIG } from "../pages/errors"; import { parseJSONC, parseTOML, readFileSync } from "../parse"; +import { RawEnvironment } from "./environment"; import { isPagesConfig, normalizeAndValidateConfig } from "./validation"; import { validatePagesConfig } from "./validation-pages"; import type { CfWorkerInit } from "../deployment-bundle/worker"; @@ -28,6 +30,18 @@ export type { RawEnvironment, } from "./environment"; +export function formatConfigSnippet( + config: RawEnvironment, + parsedFormat: Config["parsedFormat"], + spacing = true +) { + if (parsedFormat === "jsonc") { + return spacing ? JSON.stringify(config, null, 2) : JSON.stringify(config); + } else { + return TOML.stringify(config as TOML.JsonMap); + } +} + type ReadConfigCommandArgs = NormalizeAndValidateConfigArgs & { experimentalJsonConfig?: boolean | undefined; }; @@ -55,6 +69,7 @@ export function readConfig( requirePagesConfig?: boolean, hideWarnings: boolean = false ): Config { + let parsedFormat: Config["parsedFormat"] = "jsonc"; const isJsonConfigEnabled = getFlag("JSON_CONFIG_FILE") ?? args.experimentalJsonConfig; let rawConfig: RawConfig = {}; @@ -66,8 +81,10 @@ export function readConfig( try { // Load the configuration from disk if available if (configPath?.endsWith("toml")) { + parsedFormat = "toml"; rawConfig = parseTOML(readFileSync(configPath), configPath); } else if (configPath?.endsWith("json") || configPath?.endsWith("jsonc")) { + parsedFormat = "jsonc"; rawConfig = parseJSONC(readFileSync(configPath), configPath); } } catch (e) { @@ -108,7 +125,8 @@ export function readConfig( const { config, diagnostics } = normalizeAndValidateConfig( rawConfig, configPath, - args + args, + parsedFormat ); if (diagnostics.hasWarnings() && !hideWarnings) { @@ -138,6 +156,7 @@ export function readConfig( } applyPythonConfig(config, args); + config.parsedFormat = parsedFormat; return config; } diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index ebd47750cbce..f29df2af48a7 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -29,7 +29,7 @@ import { validateRequiredProperty, validateTypedArray, } from "./validation-helpers"; -import { friendlyBindingNames } from "."; +import { formatConfigSnippet, friendlyBindingNames } from "."; import type { CfWorkerInit } from "../deployment-bundle/worker"; import type { Config, DevConfig, RawConfig, RawDevConfig } from "./config"; import type { @@ -72,7 +72,8 @@ export function isPagesConfig(rawConfig: RawConfig): boolean { export function normalizeAndValidateConfig( rawConfig: RawConfig, configPath: string | undefined, - args: NormalizeAndValidateConfigArgs + args: NormalizeAndValidateConfigArgs, + parsedFormat: Config["parsedFormat"] = "toml" ): { config: Config; diagnostics: Diagnostics; @@ -172,7 +173,12 @@ export function normalizeAndValidateConfig( diagnostics, configPath, rawConfig, - isDispatchNamespace + isDispatchNamespace, + "top level", + undefined, + undefined, + undefined, + parsedFormat ); //TODO: find a better way to define the type of Args that can be passed to the normalizeAndValidateConfig() @@ -210,7 +216,8 @@ export function normalizeAndValidateConfig( envName, topLevelEnv, isLegacyEnv, - rawConfig + rawConfig, + parsedFormat ); diagnostics.addChild(envDiagnostics); } else if (!isPagesConfig(rawConfig)) { @@ -222,7 +229,8 @@ export function normalizeAndValidateConfig( envName, topLevelEnv, isLegacyEnv, - rawConfig + rawConfig, + parsedFormat ); const envNames = rawConfig.env ? `The available configured environment names are: ${JSON.stringify( @@ -1025,41 +1033,9 @@ const validateTailConsumers: ValidatorFn = (diagnostics, field, value) => { return isValid; }; -/** - * Validate top-level environment configuration and return the normalized values. - */ -function normalizeAndValidateEnvironment( - diagnostics: Diagnostics, - configPath: string | undefined, - topLevelEnv: RawEnvironment, - isDispatchNamespace: boolean -): Environment; /** * Validate the named environment configuration and return the normalized values. */ -function normalizeAndValidateEnvironment( - diagnostics: Diagnostics, - configPath: string | undefined, - rawEnv: RawEnvironment, - isDispatchNamespace: boolean, - envName: string, - topLevelEnv: Environment, - isLegacyEnv: boolean, - rawConfig: RawConfig -): Environment; -/** - * Validate the named environment configuration and return the normalized values. - */ -function normalizeAndValidateEnvironment( - diagnostics: Diagnostics, - configPath: string | undefined, - rawEnv: RawEnvironment, - isDispatchNamespace: boolean, - envName?: string, - topLevelEnv?: Environment, - isLegacyEnv?: boolean, - rawConfig?: RawConfig -): Environment; function normalizeAndValidateEnvironment( diagnostics: Diagnostics, configPath: string | undefined, @@ -1068,7 +1044,8 @@ function normalizeAndValidateEnvironment( envName = "top level", topLevelEnv?: Environment | undefined, isLegacyEnv?: boolean, - rawConfig?: RawConfig | undefined + rawConfig?: RawConfig | undefined, + parsedFormat?: Config["parsedFormat"] ): Environment { deprecated( diagnostics, @@ -1555,11 +1532,7 @@ function normalizeAndValidateEnvironment( ), }; - warnIfDurableObjectsHaveNoMigrations( - diagnostics, - environment.durable_objects, - environment.migrations - ); + warnIfDurableObjectsHaveNoMigrations(diagnostics, environment, parsedFormat); return environment; } @@ -3378,18 +3351,18 @@ const validateObservability: ValidatorFn = (diagnostics, field, value) => { function warnIfDurableObjectsHaveNoMigrations( diagnostics: Diagnostics, - durableObjects: Config["durable_objects"], - migrations: Config["migrations"] + config: Environment, + parsedFormat: Config["parsedFormat"] ) { if ( - Array.isArray(durableObjects.bindings) && - durableObjects.bindings.length > 0 + Array.isArray(config.durable_objects.bindings) && + config.durable_objects.bindings.length > 0 ) { // intrinsic [durable_objects] implies [migrations] - const exportedDurableObjects = (durableObjects.bindings || []).filter( - (binding) => !binding.script_name - ); - if (exportedDurableObjects.length > 0 && migrations.length === 0) { + const exportedDurableObjects = ( + config.durable_objects.bindings || [] + ).filter((binding) => !binding.script_name); + if (exportedDurableObjects.length > 0 && config.migrations.length === 0) { if ( !exportedDurableObjects.some( (exportedDurableObject) => @@ -3403,11 +3376,14 @@ function warnIfDurableObjectsHaveNoMigrations( diagnostics.warnings.push(dedent` In wrangler.toml, you have configured [durable_objects] exported by this Worker (${durableObjectClassnames.join(", ")}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml: - \`\`\` - [[migrations]] - tag = "v1" # Should be unique for each entry - new_classes = [${durableObjectClassnames.map((name) => `"${name}"`).join(", ")}] - \`\`\` + \`\`\` + ${formatConfigSnippet( + { + migrations: [{ tag: "v1", new_classes: durableObjectClassnames }], + }, + parsedFormat + )} + \`\`\` Refer to https://developers.cloudflare.com/durable-objects/reference/durable-objects-migrations/ for more details.`); } diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index 85059b699ea4..55fd88cda2cb 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -5,7 +5,7 @@ import { URLSearchParams } from "node:url"; import { cancel } from "@cloudflare/cli"; import { syncAssets } from "../assets"; import { fetchListResult, fetchResult } from "../cfetch"; -import { printBindings } from "../config"; +import { formatConfigSnippet, printBindings } from "../config"; import { bundleWorker } from "../deployment-bundle/bundle"; import { printBundleSize, @@ -425,7 +425,7 @@ export default async function deploy(props: Props): Promise<{ throw new UserError(`A compatibility_date is required when publishing. Add the following to your wrangler.toml file:. \`\`\` - compatibility_date = "${compatibilityDateStr}" + ${formatConfigSnippet({ compatibility_date: compatibilityDateStr }, config.parsedFormat, false)} \`\`\` Or you could pass it in your terminal as \`--compatibility-date ${compatibilityDateStr}\` See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information.`); diff --git a/packages/wrangler/src/kv/index.ts b/packages/wrangler/src/kv/index.ts index 3e47314fe535..43414643452a 100644 --- a/packages/wrangler/src/kv/index.ts +++ b/packages/wrangler/src/kv/index.ts @@ -1,7 +1,7 @@ import { Blob } from "node:buffer"; import { arrayBuffer } from "node:stream/consumers"; import { StringDecoder } from "node:string_decoder"; -import { readConfig } from "../config"; +import { formatConfigSnippet, readConfig } from "../config"; import { defineAlias, defineCommand, @@ -151,11 +151,21 @@ defineCommand({ logger.log( `Add the following to your configuration file in your kv_namespaces array${envString}:` ); - logger.log(`[[kv_namespaces]]`); - logger.log(`binding = "${getValidBindingName(args.namespace, "KV")}"`); - logger.log(`${previewString}id = "${namespaceId}"`); - // TODO: automatically write this block to the wrangler.toml config file?? + logger.log( + formatConfigSnippet( + { + kv_namespaces: [ + // @ts-expect-error intentional subset + { + binding: getValidBindingName(args.namespace, "KV"), + [`${previewString}id`]: namespaceId, + }, + ], + }, + config.parsedFormat + ) + ); }, }); diff --git a/packages/wrangler/src/queues/cli/commands/create.ts b/packages/wrangler/src/queues/cli/commands/create.ts index eca3ff70d6a9..1926de2679e0 100644 --- a/packages/wrangler/src/queues/cli/commands/create.ts +++ b/packages/wrangler/src/queues/cli/commands/create.ts @@ -1,4 +1,5 @@ -import { readConfig } from "../../../config"; +import dedent from "ts-dedent"; +import { formatConfigSnippet, readConfig } from "../../../config"; import { CommandLineArgsError } from "../../../errors"; import { logger } from "../../../logger"; import { getValidBindingName } from "../../../utils/getValidBindingName"; @@ -56,16 +57,38 @@ export async function handler( try { logger.log(`🌀 Creating queue '${args.name}'`); await createQueue(config, body); - logger.log( - `✅ Created queue '${args.name}'\n\n` + - "Configure your Worker to send messages to this queue:\n\n" + - "[[queues.producers]]\n" + - `queue = "${args.name}"\n` + - `binding = "${getValidBindingName(args.name, "queue")}"\n\n` + - "Configure your Worker to consume messages from this queue:\n\n" + - "[[queues.consumers]]\n" + - `queue = "${args.name}"` - ); + logger.log(dedent` + ✅ Created queue '${args.name}' + + Configure your Worker to send messages to this queue: + + ${formatConfigSnippet( + { + queues: { + producers: [ + { + queue: args.name, + binding: getValidBindingName(args.name, "queue"), + }, + ], + }, + }, + config.parsedFormat + )} + Configure your Worker to consume messages from this queue: + + ${formatConfigSnippet( + { + queues: { + consumers: [ + { + queue: args.name, + }, + ], + }, + }, + config.parsedFormat + )}`); } catch (e) { handleFetchError(e as { code?: number }); } diff --git a/packages/wrangler/src/r2/create.ts b/packages/wrangler/src/r2/create.ts index 1eab044a0095..5cf6b267e3a2 100644 --- a/packages/wrangler/src/r2/create.ts +++ b/packages/wrangler/src/r2/create.ts @@ -1,5 +1,6 @@ +import { dedent } from "ts-dedent"; import { printWranglerBanner } from ".."; -import { readConfig } from "../config"; +import { formatConfigSnippet, readConfig } from "../config"; import { UserError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; @@ -66,15 +67,14 @@ export async function Handler(args: HandlerOptions) { logger.log(`Creating bucket '${fullBucketName}'...`); await createR2Bucket(accountId, name, location, jurisdiction, storageClass); - logger.log( - `✅ Created bucket '${fullBucketName}' with${ + logger.log(dedent` + ✅ Created bucket '${fullBucketName}' with${ location ? ` location hint ${location} and` : `` - } default storage class of ${storageClass ? storageClass : `Standard`}.\n\n` + - "Configure your Worker to write objects to this bucket:\n\n" + - "[[r2_buckets]]\n" + - `bucket_name = "${args.name}"\n` + - `binding = "${getValidBindingName(args.name, "r2")}"` - ); + } default storage class of ${storageClass ? storageClass : `Standard`}. + + Configure your Worker to write objects to this bucket: + + ${formatConfigSnippet({ r2_buckets: [{ bucket_name: args.name, binding: getValidBindingName(args.name, "r2") }] }, config.parsedFormat)}`); await metrics.sendMetricsEvent("create r2 bucket", { sendMetrics: config.send_metrics,