From 162199289d51dbaf3f7a371d777012d0039fbdfb Mon Sep 17 00:00:00 2001 From: Daniel Walsh Date: Fri, 21 Jun 2024 15:08:55 +0100 Subject: [PATCH] Version followups - fix rollback, version view improvements (#6118) --- .changeset/brave-doors-visit.md | 7 + .../helpers/msw/handlers/versions.ts | 79 +- .../wrangler/src/__tests__/rollback.test.ts | 2 +- .../deployments/deployments.list.test.ts | 2 +- .../deployments/deployments.status.test.ts | 2 +- .../__tests__/versions/secrets/bulk.test.ts | 6 +- .../__tests__/versions/secrets/delete.test.ts | 9 +- .../__tests__/versions/secrets/put.test.ts | 15 +- .../versions/versions.deploy.test.ts | 2 +- .../__tests__/versions/versions.view.test.ts | 682 ++++++++++++++++-- packages/wrangler/src/versions/api.ts | 71 +- .../wrangler/src/versions/rollback/index.ts | 50 +- .../wrangler/src/versions/secrets/bulk.ts | 5 +- .../wrangler/src/versions/secrets/delete.ts | 5 +- packages/wrangler/src/versions/secrets/put.ts | 5 +- packages/wrangler/src/versions/view.ts | 202 +++++- 16 files changed, 974 insertions(+), 170 deletions(-) create mode 100644 .changeset/brave-doors-visit.md diff --git a/.changeset/brave-doors-visit.md b/.changeset/brave-doors-visit.md new file mode 100644 index 000000000000..18135626de19 --- /dev/null +++ b/.changeset/brave-doors-visit.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: rollback in the case of a secret change, the prompt meant to show was not showing due to the spinner in an interactive env. It will now properly show. + +chore: improve the view of `wrangler versions view` and change up copy a little for `versions secret` commands. diff --git a/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts b/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts index 8dea63eaa3fc..afcd2a78d459 100644 --- a/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts +++ b/packages/wrangler/src/__tests__/helpers/msw/handlers/versions.ts @@ -266,33 +266,62 @@ export const mswListVersions = http.get( } ); -export const mswGetVersion = http.get( - "*/accounts/:accountId/workers/scripts/:workerName/versions/:versionId", - ({ params }) => { - const versionId = String(params["versionId"]); +export const mswGetVersion = (version?: ApiVersion) => + http.get( + "*/accounts/:accountId/workers/scripts/:workerName/versions/:versionId", + ({ params }) => { + const versionId = String(params["versionId"]); - if (versionId === "ffffffff-ffff-ffff-ffff-ffffffffffff") { - return HttpResponse.json(createFetchResult({}, false)); - } + if (versionId === "ffffffff-ffff-ffff-ffff-ffffffffffff") { + return HttpResponse.json(createFetchResult({}, false)); + } - return HttpResponse.json( - createFetchResult({ - id: versionId, - number: "1701-E", - annotations: { - "workers/triggered_by": "upload", - }, - metadata: { - author_id: "Picard-Gamma-6-0-7-3", - author_email: "Jean-Luc-Picard@federation.org", - source: "wrangler", - created_on: "2021-01-01T00:00:00.000000Z", - modified_on: "2021-01-01T00:00:00.000000Z", - }, - }) - ); - } -); + return HttpResponse.json( + createFetchResult( + version ?? + ({ + id: versionId, + number: 1, + annotations: { + "workers/triggered_by": "upload", + }, + metadata: { + author_id: "Picard-Gamma-6-0-7-3", + author_email: "Jean-Luc-Picard@federation.org", + source: "wrangler", + created_on: "2021-01-01T00:00:00.000000Z", + modified_on: "2021-01-01T00:00:00.000000Z", + }, + resources: { + bindings: [ + { + type: "analytics_engine", + name: "ANALYTICS", + dataset: "analytics_dataset", + }, + { + type: "kv_namespace", + name: "KV", + namespace_id: "kv-namespace-id", + }, + ], + script: { + etag: "aaabbbccc", + handlers: ["fetch", "scheduled"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2020-01-01", + compatibility_flags: ["test", "flag"], + usage_model: "standard", + limits: { cpu_ms: 50 }, + }, + }, + } as ApiVersion) + ) + ); + } + ); export const mswPostNewDeployment = http.post( "*/accounts/:accountId/workers/scripts/:workerName/deployments", diff --git a/packages/wrangler/src/__tests__/rollback.test.ts b/packages/wrangler/src/__tests__/rollback.test.ts index 52423e5f7fe1..e79956d1d7af 100644 --- a/packages/wrangler/src/__tests__/rollback.test.ts +++ b/packages/wrangler/src/__tests__/rollback.test.ts @@ -1,6 +1,6 @@ import { http, HttpResponse } from "msw"; import { describe, expect, test } from "vitest"; -import { CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE } from "../versions/api"; +import { CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE } from "../versions/rollback"; import { collectCLIOutput } from "./helpers/collect-cli-output"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; import { mockConfirm, mockPrompt } from "./helpers/mock-dialogs"; diff --git a/packages/wrangler/src/__tests__/versions/deployments/deployments.list.test.ts b/packages/wrangler/src/__tests__/versions/deployments/deployments.list.test.ts index d2c2c888e614..f2c5e3e6b5fd 100644 --- a/packages/wrangler/src/__tests__/versions/deployments/deployments.list.test.ts +++ b/packages/wrangler/src/__tests__/versions/deployments/deployments.list.test.ts @@ -15,7 +15,7 @@ describe("deployments list", () => { const std = collectCLIOutput(); beforeEach(() => { - msw.use(mswListNewDeployments, mswGetVersion); + msw.use(mswListNewDeployments, mswGetVersion()); }); describe("without wrangler.toml", () => { diff --git a/packages/wrangler/src/__tests__/versions/deployments/deployments.status.test.ts b/packages/wrangler/src/__tests__/versions/deployments/deployments.status.test.ts index 31bb231872a8..99e3ed8d75e0 100644 --- a/packages/wrangler/src/__tests__/versions/deployments/deployments.status.test.ts +++ b/packages/wrangler/src/__tests__/versions/deployments/deployments.status.test.ts @@ -15,7 +15,7 @@ describe("deployments list", () => { const std = collectCLIOutput(); beforeEach(() => { - msw.use(mswListNewDeployments, mswGetVersion); + msw.use(mswListNewDeployments, mswGetVersion()); }); describe("without wrangler.toml", () => { diff --git a/packages/wrangler/src/__tests__/versions/secrets/bulk.test.ts b/packages/wrangler/src/__tests__/versions/secrets/bulk.test.ts index 7ce4db4af5f3..11f332589dbd 100644 --- a/packages/wrangler/src/__tests__/versions/secrets/bulk.test.ts +++ b/packages/wrangler/src/__tests__/versions/secrets/bulk.test.ts @@ -67,7 +67,8 @@ describe("versions secret put", () => { ✨ Successfully created secret for key: SECRET_1 ✨ Successfully created secret for key: SECRET_2 ✨ Successfully created secret for key: SECRET_3 - ✨ Success! Created version id with 3 secrets. To deploy this version to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with 3 secrets. + ➡️ To deploy this version to production traffic use the command \\"wrangler versions deploy --x-versions\\"." ` ); expect(std.err).toMatchInlineSnapshot(`""`); @@ -104,7 +105,8 @@ describe("versions secret put", () => { ✨ Successfully created secret for key: SECRET_1 ✨ Successfully created secret for key: SECRET_2 ✨ Successfully created secret for key: SECRET_3 - ✨ Success! Created version id with 3 secrets. To deploy this version to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with 3 secrets. + ➡️ To deploy this version to production traffic use the command \\"wrangler versions deploy --x-versions\\"." ` ); expect(std.err).toMatchInlineSnapshot(`""`); diff --git a/packages/wrangler/src/__tests__/versions/secrets/delete.test.ts b/packages/wrangler/src/__tests__/versions/secrets/delete.test.ts index f65311183d6b..b9466286c8de 100644 --- a/packages/wrangler/src/__tests__/versions/secrets/delete.test.ts +++ b/packages/wrangler/src/__tests__/versions/secrets/delete.test.ts @@ -43,7 +43,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Deleting the secret SECRET on the Worker script-name - ✨ Success! Created version id with deleted secret SECRET. To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with deleted secret SECRET. + ➡️ To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -70,7 +71,8 @@ describe("versions secret put", () => { "? Are you sure you want to permanently delete the secret SECRET on the Worker script-name? 🤖 Using fallback value in non-interactive context: yes 🌀 Deleting the secret SECRET on the Worker script-name - ✨ Success! Created version id with deleted secret SECRET. To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with deleted secret SECRET. + ➡️ To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -96,7 +98,8 @@ describe("versions secret put", () => { "? Are you sure you want to permanently delete the secret SECRET on the Worker script-name? 🤖 Using fallback value in non-interactive context: yes 🌀 Deleting the secret SECRET on the Worker script-name - ✨ Success! Created version id with deleted secret SECRET. To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with deleted secret SECRET. + ➡️ To deploy this version without the secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); diff --git a/packages/wrangler/src/__tests__/versions/secrets/put.test.ts b/packages/wrangler/src/__tests__/versions/secrets/put.test.ts index e6993447a3e2..91d29e45cb11 100644 --- a/packages/wrangler/src/__tests__/versions/secrets/put.test.ts +++ b/packages/wrangler/src/__tests__/versions/secrets/put.test.ts @@ -44,7 +44,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Creating the secret for the Worker \\"script-name\\" - ✨ Success! Created version id with secret NEW_SECRET. To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with secret NEW_SECRET. + ➡️ To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -108,7 +109,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Creating the secret for the Worker \\"script-name\\" - ✨ Success! Created version id with secret NEW_SECRET. To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with secret NEW_SECRET. + ➡️ To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -143,7 +145,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Creating the secret for the Worker \\"script-name\\" - ✨ Success! Created version id with secret NEW_SECRET. To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with secret NEW_SECRET. + ➡️ To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -181,7 +184,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Creating the secret for the Worker \\"script-name\\" - ✨ Success! Created version id with secret NEW_SECRET. To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with secret NEW_SECRET. + ➡️ To deploy this version with secret NEW_SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); @@ -216,7 +220,8 @@ describe("versions secret put", () => { expect(std.out).toMatchInlineSnapshot(` "🌀 Creating the secret for the Worker \\"script-name\\" - ✨ Success! Created version id with secret SECRET. To deploy this version with secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." + ✨ Success! Created version id with secret SECRET. + ➡️ To deploy this version with secret SECRET to production traffic use the command \\"wrangler versions deploy --x-versions\\"." `); expect(std.err).toMatchInlineSnapshot(`""`); }); diff --git a/packages/wrangler/src/__tests__/versions/versions.deploy.test.ts b/packages/wrangler/src/__tests__/versions/versions.deploy.test.ts index 0c7e31569b74..1155d46ddfa7 100644 --- a/packages/wrangler/src/__tests__/versions/versions.deploy.test.ts +++ b/packages/wrangler/src/__tests__/versions/versions.deploy.test.ts @@ -42,7 +42,7 @@ describe("versions deploy", () => { msw.use( mswListNewDeployments, mswListVersions, - mswGetVersion, + mswGetVersion(), mswPostNewDeployment, mswPatchNonVersionedScriptSettings ); diff --git a/packages/wrangler/src/__tests__/versions/versions.view.test.ts b/packages/wrangler/src/__tests__/versions/versions.view.test.ts index b5da74361183..59dd98b63e10 100644 --- a/packages/wrangler/src/__tests__/versions/versions.view.test.ts +++ b/packages/wrangler/src/__tests__/versions/versions.view.test.ts @@ -14,11 +14,9 @@ describe("versions view", () => { mockConsoleMethods(); const std = collectCLIOutput(); - beforeEach(() => { - msw.use(mswGetVersion); - }); - describe("without wrangler.toml", () => { + beforeEach(() => msw.use(mswGetVersion())); + test("fails with no args", async () => { const result = runWrangler( "versions view --experimental-gradual-rollouts" @@ -69,14 +67,27 @@ describe("versions view", () => { await expect(result).resolves.toBeUndefined(); expect(std.out).toMatchInlineSnapshot(` - "Version ID: 10000000-0000-0000-0000-000000000000 - Created: 2021-01-01T00:00:00.000Z - Author: Jean-Luc-Picard@federation.org - Source: Upload - Tag: - - Message: - - " - `); + "Version ID: 10000000-0000-0000-0000-000000000000 + Created: 2021-01-01T00:00:00.000Z + Author: Jean-Luc-Picard@federation.org + Source: Upload + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, scheduled + Compatibility Date: 2020-01-01 + Compatibility Flags: test, flag + ------------------------- bindings ------------------------- + [[analytics_engine_datasets]] + binding = ANALYTICS + dataset = analytics_dataset + + [[kv_namespaces]] + binding = \\"KV\\" + id = \\"kv-namespace-id\\" + + " + `); expect(normalizeOutput(std.err)).toMatchInlineSnapshot(`""`); }); @@ -89,27 +100,63 @@ describe("versions view", () => { await expect(result).resolves.toBeUndefined(); expect(std.out).toMatchInlineSnapshot(` - "{ - \\"id\\": \\"10000000-0000-0000-0000-000000000000\\", - \\"number\\": \\"1701-E\\", - \\"annotations\\": { - \\"workers/triggered_by\\": \\"upload\\" - }, - \\"metadata\\": { - \\"author_id\\": \\"Picard-Gamma-6-0-7-3\\", - \\"author_email\\": \\"Jean-Luc-Picard@federation.org\\", - \\"source\\": \\"wrangler\\", - \\"created_on\\": \\"2021-01-01T00:00:00.000000Z\\", - \\"modified_on\\": \\"2021-01-01T00:00:00.000000Z\\" - } - } - " - `); + "{ + \\"id\\": \\"10000000-0000-0000-0000-000000000000\\", + \\"number\\": 1, + \\"annotations\\": { + \\"workers/triggered_by\\": \\"upload\\" + }, + \\"metadata\\": { + \\"author_id\\": \\"Picard-Gamma-6-0-7-3\\", + \\"author_email\\": \\"Jean-Luc-Picard@federation.org\\", + \\"source\\": \\"wrangler\\", + \\"created_on\\": \\"2021-01-01T00:00:00.000000Z\\", + \\"modified_on\\": \\"2021-01-01T00:00:00.000000Z\\" + }, + \\"resources\\": { + \\"bindings\\": [ + { + \\"type\\": \\"analytics_engine\\", + \\"name\\": \\"ANALYTICS\\", + \\"dataset\\": \\"analytics_dataset\\" + }, + { + \\"type\\": \\"kv_namespace\\", + \\"name\\": \\"KV\\", + \\"namespace_id\\": \\"kv-namespace-id\\" + } + ], + \\"script\\": { + \\"etag\\": \\"aaabbbccc\\", + \\"handlers\\": [ + \\"fetch\\", + \\"scheduled\\" + ], + \\"last_deployed_from\\": \\"api\\" + }, + \\"script_runtime\\": { + \\"compatibility_date\\": \\"2020-01-01\\", + \\"compatibility_flags\\": [ + \\"test\\", + \\"flag\\" + ], + \\"usage_model\\": \\"standard\\", + \\"limits\\": { + \\"cpu_ms\\": 50 + } + } + } + } + " + `); }); }); describe("with wrangler.toml", () => { - beforeEach(() => writeWranglerToml()); + beforeEach(() => { + msw.use(mswGetVersion()); + writeWranglerToml(); + }); test("fails with no args", async () => { const result = runWrangler( @@ -133,14 +180,27 @@ describe("versions view", () => { await expect(result).resolves.toBeUndefined(); expect(std.out).toMatchInlineSnapshot(` - "Version ID: 10000000-0000-0000-0000-000000000000 - Created: 2021-01-01T00:00:00.000Z - Author: Jean-Luc-Picard@federation.org - Source: Upload - Tag: - - Message: - - " - `); + "Version ID: 10000000-0000-0000-0000-000000000000 + Created: 2021-01-01T00:00:00.000Z + Author: Jean-Luc-Picard@federation.org + Source: Upload + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, scheduled + Compatibility Date: 2020-01-01 + Compatibility Flags: test, flag + ------------------------- bindings ------------------------- + [[analytics_engine_datasets]] + binding = ANALYTICS + dataset = analytics_dataset + + [[kv_namespaces]] + binding = \\"KV\\" + id = \\"kv-namespace-id\\" + + " + `); expect(normalizeOutput(std.err)).toMatchInlineSnapshot(`""`); }); @@ -167,22 +227,538 @@ describe("versions view", () => { await expect(result).resolves.toBeUndefined(); expect(std.out).toMatchInlineSnapshot(` - "{ - \\"id\\": \\"10000000-0000-0000-0000-000000000000\\", - \\"number\\": \\"1701-E\\", - \\"annotations\\": { - \\"workers/triggered_by\\": \\"upload\\" - }, - \\"metadata\\": { - \\"author_id\\": \\"Picard-Gamma-6-0-7-3\\", - \\"author_email\\": \\"Jean-Luc-Picard@federation.org\\", - \\"source\\": \\"wrangler\\", - \\"created_on\\": \\"2021-01-01T00:00:00.000000Z\\", - \\"modified_on\\": \\"2021-01-01T00:00:00.000000Z\\" - } - } - " - `); + "{ + \\"id\\": \\"10000000-0000-0000-0000-000000000000\\", + \\"number\\": 1, + \\"annotations\\": { + \\"workers/triggered_by\\": \\"upload\\" + }, + \\"metadata\\": { + \\"author_id\\": \\"Picard-Gamma-6-0-7-3\\", + \\"author_email\\": \\"Jean-Luc-Picard@federation.org\\", + \\"source\\": \\"wrangler\\", + \\"created_on\\": \\"2021-01-01T00:00:00.000000Z\\", + \\"modified_on\\": \\"2021-01-01T00:00:00.000000Z\\" + }, + \\"resources\\": { + \\"bindings\\": [ + { + \\"type\\": \\"analytics_engine\\", + \\"name\\": \\"ANALYTICS\\", + \\"dataset\\": \\"analytics_dataset\\" + }, + { + \\"type\\": \\"kv_namespace\\", + \\"name\\": \\"KV\\", + \\"namespace_id\\": \\"kv-namespace-id\\" + } + ], + \\"script\\": { + \\"etag\\": \\"aaabbbccc\\", + \\"handlers\\": [ + \\"fetch\\", + \\"scheduled\\" + ], + \\"last_deployed_from\\": \\"api\\" + }, + \\"script_runtime\\": { + \\"compatibility_date\\": \\"2020-01-01\\", + \\"compatibility_flags\\": [ + \\"test\\", + \\"flag\\" + ], + \\"usage_model\\": \\"standard\\", + \\"limits\\": { + \\"cpu_ms\\": 50 + } + } + } + } + " + `); + }); + }); + + describe("test output", () => { + test("no secrets, bindings or compat info is logged if not existing", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + " + `); + }); + + test("compat date is logged if provided", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2000-00-00", + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + Compatibility Date: 2000-00-00 + " + `); + }); + + test("compat flag is logged if provided", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2000-00-00", + compatibility_flags: ["flag_1", "flag_2"], + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + Compatibility Date: 2000-00-00 + Compatibility Flags: flag_1, flag_2 + " + `); + }); + + test("secrets are logged if provided", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [ + { type: "secret_text", name: "SECRET_ONE", text: "" }, + { type: "secret_text", name: "SECRET_TWO", text: "" }, + ], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2000-00-00", + compatibility_flags: ["flag_1", "flag_2"], + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + Compatibility Date: 2000-00-00 + Compatibility Flags: flag_1, flag_2 + ------------------------- secrets ------------------------- + Secret Name: SECRET_ONE + Secret Name: SECRET_TWO + " + `); + }); + + test("env vars are logged if provided", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [ + { type: "plain_text", name: "VAR_ONE", text: "var-one" }, + { type: "plain_text", name: "VAR_TWO", text: "var-one" }, + ], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2000-00-00", + compatibility_flags: ["flag_1", "flag_2"], + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + Compatibility Date: 2000-00-00 + Compatibility Flags: flag_1, flag_2 + ------------------------- bindings ------------------------- + [vars] + VAR_ONE = \\"var-one\\" + VAR_TWO = \\"var-one\\" + " + `); + }); + + test("bindings are logged if provided", async () => { + msw.use( + mswGetVersion({ + id: "ce15c78b-cc43-4f60-b5a9-15ce4f298c2a", + metadata: { + author_email: "test@test.com", + author_id: "123", + created_on: "2020-01-01 00:00:00", + modified_on: "2020-01-01 00:00:00", + source: "api", + }, + number: 2, + resources: { + bindings: [ + { type: "ai", name: "AI" }, + { type: "analytics_engine", name: "AE", dataset: "datset" }, + { type: "browser", name: "BROWSER" }, + { type: "d1", name: "D1", id: "d1-id" }, + { + type: "dispatch_namespace", + name: "WFP", + namespace: "wfp-namespace", + }, + { + type: "dispatch_namespace", + name: "WFP_2", + namespace: "wfp-namespace", + outbound: { worker: { service: "outbound-worker" } }, + }, + { + type: "dispatch_namespace", + name: "WFP_3", + namespace: "wfp-namespace", + outbound: { + worker: { service: "outbound-worker" }, + params: [{ name: "paramOne" }, { name: "paramTwo" }], + }, + }, + { + type: "durable_object_namespace", + name: "DO", + class_name: "DurableObject", + }, + { + type: "durable_object_namespace", + name: "DO_2", + class_name: "DurableObject", + script_name: "other-worker", + }, + { type: "hyperdrive", name: "HYPERDRIVE", id: "hyperdrive-id" }, + { type: "kv_namespace", name: "KV", namespace_id: "kv-id" }, + { + type: "mtls_certificate", + name: "MTLS", + certificate_id: "mtls-id", + }, + { type: "queue", name: "QUEUE", queue_name: "queue" }, + { + type: "queue", + name: "QUEUE_2", + queue_name: "queue", + delivery_delay: 60, + }, + { type: "r2_bucket", name: "R2", bucket_name: "r2-bucket" }, + { + type: "r2_bucket", + name: "R2_2", + bucket_name: "r2-bucket", + jurisdiction: "eu", + }, + { type: "send_email", name: "MAIL" }, + { + type: "send_email", + name: "MAIL_2", + destination_address: "dest@example.com", + }, + { + type: "send_email", + name: "MAIL_3", + destination_address: "dest@example.com", + allowed_destination_addresses: ["1@a.com", "2@a.com"], + }, + { type: "service", name: "SERVICE", service: "worker" }, + { + type: "service", + name: "SERVICE_2", + service: "worker", + entrypoint: "Enterypoint", + }, + { type: "vectorize", name: "VECTORIZE", index_name: "index" }, + { type: "version_metadata", name: "VERSION_METADATA" }, + ], + script: { + etag: "etag", + handlers: ["fetch", "queue"], + last_deployed_from: "api", + }, + script_runtime: { + compatibility_date: "2000-00-00", + compatibility_flags: ["flag_1", "flag_2"], + usage_model: "standard", + limits: {}, + }, + }, + }) + ); + + const result = runWrangler( + "versions view 10000000-0000-0000-0000-000000000000 --name test --experimental-versions" + ); + + await expect(result).resolves.toBeUndefined(); + + expect(std.out).toMatchInlineSnapshot(` + "Version ID: ce15c78b-cc43-4f60-b5a9-15ce4f298c2a + Created: 2020-01-01T00:00:00.000Z + Author: test@test.com + Source: API 📡 + Tag: - + Message: - + ------------------------------------------------------------ + Handlers: fetch, queue + Compatibility Date: 2000-00-00 + Compatibility Flags: flag_1, flag_2 + ------------------------- bindings ------------------------- + [ai] + binding = AI + + [[analytics_engine_datasets]] + binding = AE + dataset = datset + + [browser] + binding = \\"BROWSER\\" + + [[d1_databases]] + binding = \\"D1\\" + database_id = \\"d1-id\\" + + [[dispatch_namespaces]] + binding = \\"WFP\\" + namespce = \\"wfp-namespace\\" + + [[dispatch_namespaces]] + binding = \\"WFP_2\\" + namespce = \\"wfp-namespace\\" + outbound = { service = \\"outbound-worker\\" } + + [[dispatch_namespaces]] + binding = \\"WFP_3\\" + namespce = \\"wfp-namespace\\" + outbound = { service = \\"outbound-worker\\", parameters = [paramOne, paramTwo] } + + [[durable_objects.bindings]] + name = \\"DO\\" + class_name = \\"DurableObject\\" + + [[durable_objects.bindings]] + name = \\"DO_2\\" + class_name = \\"DurableObject\\" + script_name = \\"other-worker\\" + + [[hyperdrive]] + binding = \\"HYPERDRIVE\\" + id = \\"hyperdrive-id\\" + + [[kv_namespaces]] + binding = \\"KV\\" + id = \\"kv-id\\" + + [[mtls_certificates]] + binding = \\"MTLS\\" + certificate_id = \\"mtls-id\\" + + [[queues.producers]] + binding = \\"QUEUE\\" + queue = \\"queue\\" + + [[queues.producers]] + binding = \\"QUEUE_2\\" + queue = \\"queue\\" + delivery_delay = 60 + + [[r2_buckets]] + binding = \\"R2\\" + bucket_name = \\"r2-bucket\\" + + [[r2_buckets]] + binding = \\"R2_2\\" + bucket_name = \\"r2-bucket\\" + jurisdiction = \\"eu\\" + + [[send_email]] + name = \\"MAIL\\" + + [[send_email]] + name = \\"MAIL_2\\" + destination_address = \\"dest@example.com\\" + + [[send_email]] + name = \\"MAIL_3\\" + destination_address = \\"dest@example.com\\" + allowed_destination_addresses = [\\"1@a.com\\", \\"2@a.com\\"] + + [[services]] + binding = \\"SERVICE\\" + service = \\"SERVICE\\" + + [[services]] + binding = \\"SERVICE_2\\" + service = \\"SERVICE_2\\" + entrypoint = \\"Enterypoint\\" + + [[vectorize]] + binding = \\"VECTORIZE\\" + index_name = \\"index\\" + + [version_metadata] + binding = \\"VERSION_METADATA\\" + + " + `); }); }); }); diff --git a/packages/wrangler/src/versions/api.ts b/packages/wrangler/src/versions/api.ts index f6d600575eba..ac8d69b17d63 100644 --- a/packages/wrangler/src/versions/api.ts +++ b/packages/wrangler/src/versions/api.ts @@ -1,6 +1,4 @@ import { fetchResult } from "../cfetch"; -import { confirm } from "../dialogs"; -import { APIError } from "../parse"; import type { TailConsumer } from "../config/environment"; import type { ApiDeployment, @@ -10,8 +8,6 @@ import type { VersionId, } from "./types"; -export const CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE = 10220; - export async function fetchVersion( accountId: string, workerName: string, @@ -111,59 +107,22 @@ export async function createDeployment( message: string | undefined, force?: boolean ) { - try { - return await fetchResult( - `/accounts/${accountId}/workers/scripts/${workerName}/deployments${force ? "?force=true" : ""}`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - strategy: "percentage", - versions: Array.from(versionTraffic).map( - ([version_id, percentage]) => ({ version_id, percentage }) - ), - annotations: { - "workers/message": message, - }, - }), - } - ); - } catch (e) { - if ( - e instanceof APIError && - e.code === CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE - ) { - // This is not great but is the best way I could think to handle for now - const errorMsg = e.notes[0].text.replace( - ` [code: ${CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE}]`, - "" - ); - const targetString = "The following secrets have changed:"; - const changedSecrets = errorMsg - .substring(errorMsg.indexOf(targetString) + targetString.length + 1) - .split(", "); - - const confirmed = await confirm( - "The following secrets have changed since the target version was deployed. " + - `Please confirm you wish to continue with the rollback\n` + - changedSecrets.map((secret) => ` * ${secret}`).join("\n") - ); - - if (confirmed) { - return await createDeployment( - accountId, - workerName, - versionTraffic, - message, - true - ); - } else { - throw new Error("Aborting rollback..."); - } - } else { - throw e; + return await fetchResult( + `/accounts/${accountId}/workers/scripts/${workerName}/deployments${force ? "?force=true" : ""}`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + strategy: "percentage", + versions: Array.from(versionTraffic).map( + ([version_id, percentage]) => ({ version_id, percentage }) + ), + annotations: { + "workers/message": message, + }, + }), } - } + ); } type NonVersionedScriptSettings = { diff --git a/packages/wrangler/src/versions/rollback/index.ts b/packages/wrangler/src/versions/rollback/index.ts index e02aab4f9f37..90bf2e07e2c7 100644 --- a/packages/wrangler/src/versions/rollback/index.ts +++ b/packages/wrangler/src/versions/rollback/index.ts @@ -2,6 +2,8 @@ import * as cli from "@cloudflare/cli"; import { spinnerWhile } from "@cloudflare/cli/interactive"; import { confirm, prompt } from "../../dialogs"; import { UserError } from "../../errors"; +import { logger } from "../../logger"; +import { APIError } from "../../parse"; import { requireAuth } from "../../user"; import { createDeployment, fetchLatestDeployments, fetchVersion } from "../api"; import { printLatestDeployment, printVersions } from "../deploy"; @@ -13,6 +15,8 @@ import type { } from "../../yargs-types"; import type { VersionId } from "../types"; +export const CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE = 10220; + export type VersionsRollbackArgs = StrictYargsOptionsToInterface< typeof versionsRollbackOptions >; @@ -104,13 +108,45 @@ export async function versionsRollbackHandler(args: VersionsRollbackArgs) { return; } - await spinnerWhile({ - async promise() { - await createDeployment(accountId, workerName, rollbackTraffic, message); - }, - startMessage: `Performing rollback`, - endMessage: "", - }); + logger.log("Performing rollback..."); + try { + await createDeployment(accountId, workerName, rollbackTraffic, message); + } catch (e) { + if ( + e instanceof APIError && + e.code === CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE + ) { + // This is not great but is the best way I could think to handle for now + const errorMsg = e.notes[0].text.replace( + ` [code: ${CANNOT_ROLLBACK_WITH_MODIFIED_SECERT_CODE}]`, + "" + ); + const targetString = "The following secrets have changed:"; + const changedSecrets = errorMsg + .substring(errorMsg.indexOf(targetString) + targetString.length + 1) + .split(", "); + + const secretConfirmation = await confirm( + "The following secrets have changed since the target version was deployed. " + + `Please confirm you wish to continue with the rollback\n` + + changedSecrets.map((secret) => ` * ${secret}`).join("\n") + ); + + if (secretConfirmation) { + await createDeployment( + accountId, + workerName, + rollbackTraffic, + message, + true + ); + } else { + cli.cancel("Aborting rollback..."); + } + } else { + throw e; + } + } cli.success( `Worker Version ${versionId} has been deployed to 100% of traffic.` diff --git a/packages/wrangler/src/versions/secrets/bulk.ts b/packages/wrangler/src/versions/secrets/bulk.ts index 17bcb4e3b44e..9d9e68a7245c 100644 --- a/packages/wrangler/src/versions/secrets/bulk.ts +++ b/packages/wrangler/src/versions/secrets/bulk.ts @@ -114,7 +114,7 @@ export async function versionsSecretPutBulkHandler( scriptName, versionId: latestVersion.id, secrets, - versionMessage: args.message, + versionMessage: args.message ?? `Bulk updated ${secrets.length} secrets`, versionTag: args.tag, sendMetrics: config.send_metrics, }); @@ -123,6 +123,7 @@ export async function versionsSecretPutBulkHandler( logger.log(`✨ Successfully created secret for key: ${secret.name}`); } logger.log( - `✨ Success! Created version ${newVersion.id} with ${secrets.length} secrets. To deploy this version to production traffic use the command "wrangler versions deploy --x-versions".` + `✨ Success! Created version ${newVersion.id} with ${secrets.length} secrets.` + + `\n➡️ To deploy this version to production traffic use the command "wrangler versions deploy --x-versions".` ); } diff --git a/packages/wrangler/src/versions/secrets/delete.ts b/packages/wrangler/src/versions/secrets/delete.ts index e100c14ae379..fe00d5d99ff0 100644 --- a/packages/wrangler/src/versions/secrets/delete.ts +++ b/packages/wrangler/src/versions/secrets/delete.ts @@ -105,14 +105,15 @@ export async function versionsSecretDeleteHandler( scriptName, versionId: latestVersion.id, secrets: newSecrets, - versionMessage: args.message, + versionMessage: args.message ?? `Deleted secret "${args.key}"`, versionTag: args.tag, sendMetrics: config.send_metrics, overrideAllSecrets: true, }); logger.log( - `✨ Success! Created version ${newVersion.id} with deleted secret ${args.key}. To deploy this version without the secret ${args.key} to production traffic use the command "wrangler versions deploy --x-versions".` + `✨ Success! Created version ${newVersion.id} with deleted secret ${args.key}.` + + `\n➡️ To deploy this version without the secret ${args.key} to production traffic use the command "wrangler versions deploy --x-versions".` ); } } diff --git a/packages/wrangler/src/versions/secrets/put.ts b/packages/wrangler/src/versions/secrets/put.ts index 367a178fc706..4ceee3b534f9 100644 --- a/packages/wrangler/src/versions/secrets/put.ts +++ b/packages/wrangler/src/versions/secrets/put.ts @@ -87,12 +87,13 @@ export async function versionsSecretPutHandler( scriptName, versionId: latestVersion.id, secrets: [{ name: args.key, value: secretValue }], - versionMessage: args.message, + versionMessage: args.message ?? `Updated secret "${args.key}"`, versionTag: args.tag, sendMetrics: config.send_metrics, }); logger.log( - `✨ Success! Created version ${newVersion.id} with secret ${args.key}. To deploy this version with secret ${args.key} to production traffic use the command "wrangler versions deploy --x-versions".` + `✨ Success! Created version ${newVersion.id} with secret ${args.key}.` + + `\n➡️ To deploy this version with secret ${args.key} to production traffic use the command "wrangler versions deploy --x-versions".` ); } diff --git a/packages/wrangler/src/versions/view.ts b/packages/wrangler/src/versions/view.ts index a96ffee1455e..0f84fddf6085 100644 --- a/packages/wrangler/src/versions/view.ts +++ b/packages/wrangler/src/versions/view.ts @@ -6,6 +6,7 @@ import { requireAuth } from "../user"; import formatLabelledValues from "../utils/render-labelled-values"; import { fetchVersion } from "./api"; import { getConfig, getVersionSource } from "./list"; +import type { WorkerMetadataBinding } from "../deployment-bundle/create-worker-upload-form"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, @@ -67,14 +68,197 @@ export async function versionsViewHandler(args: VersionsViewArgs) { return; } - const formattedVersion = formatLabelledValues({ - "Version ID": version.id, - Created: new Date(version.metadata["created_on"]).toISOString(), - Author: version.metadata.author_email, - Source: getVersionSource(version), - Tag: version.annotations?.["workers/tag"] || BLANK_INPUT, - Message: version.annotations?.["workers/message"] || BLANK_INPUT, - }); + logRaw( + formatLabelledValues({ + "Version ID": version.id, + Created: new Date(version.metadata["created_on"]).toISOString(), + Author: version.metadata.author_email, + Source: getVersionSource(version), + Tag: version.annotations?.["workers/tag"] || BLANK_INPUT, + Message: version.annotations?.["workers/message"] || BLANK_INPUT, + }) + ); + logRaw("------------------------------------------------------------"); + const scriptInfo: ScriptInfoLog = { + Handlers: version.resources.script.handlers.join(", "), + }; + if (version.resources.script_runtime.compatibility_date) { + scriptInfo["Compatibility Date"] = + version.resources.script_runtime.compatibility_date; + } + if (version.resources.script_runtime.compatibility_flags) { + scriptInfo["Compatibility Flags"] = + version.resources.script_runtime.compatibility_flags.join(", "); + } + logRaw(formatLabelledValues(scriptInfo)); + + const secrets = version.resources.bindings.filter( + (binding) => binding.type === "secret_text" + ); + if (secrets.length > 0) { + logRaw("------------------------- secrets -------------------------"); + for (const secret of secrets) { + logRaw( + formatLabelledValues({ + "Secret Name": secret.name, + }) + ); + } + } + + const bindings = version.resources.bindings.filter( + (binding) => binding.type !== "secret_text" + ); + if (bindings.length > 0) { + logRaw("------------------------- bindings -------------------------"); + // env vars are done differently so target them first + const envVars = bindings.filter((binding) => binding.type === "plain_text"); + if (envVars.length > 0) { + logRaw( + `[vars]\n` + + // ts is having issues typing from the filter + (envVars as { type: "plain_text"; name: string; text: string }[]) + .map((envVar) => `${envVar.name} = "${envVar.text}"`) + .join("\n") + ); + } + + // Filter out env vars since they got handled above + const restOfBindings = bindings.filter( + (binding) => binding.type !== "plain_text" + ); + for (const binding of restOfBindings) { + const output = printBindingAsToml(binding); + if (output !== null) { + logRaw(output); + logRaw(""); + } + } + } +} + +type ScriptInfoLog = { + Handlers: string; + "Compatibility Date"?: string; + "Compatibility Flags"?: string; +}; - logRaw(formattedVersion); +function printBindingAsToml(binding: WorkerMetadataBinding) { + switch (binding.type) { + case "ai": + return "[ai]" + `\nbinding = ${binding.name}`; + + case "analytics_engine": + return ( + "[[analytics_engine_datasets]]" + + `\nbinding = ${binding.name}` + + (binding.dataset ? `\ndataset = ${binding.dataset}` : "") + ); + + case "browser": + return "[browser]" + `\nbinding = "${binding.name}"`; + + case "d1": + return ( + "[[d1_databases]]" + + `\nbinding = "${binding.name}"` + + `\ndatabase_id = "${binding.id}"` + ); + + case "dispatch_namespace": + return ( + "[[dispatch_namespaces]]" + + `\nbinding = "${binding.name}"` + + `\nnamespce = "${binding.namespace}"` + + (binding.outbound + ? `\noutbound = { service = "${binding.outbound.worker.service}"` + + (binding.outbound.params + ? `, parameters = [${binding.outbound.params.map((param) => param.name).join(", ")}]` + : "") + + " }" + : "") + ); + + case "durable_object_namespace": + return ( + "[[durable_objects.bindings]]" + + `\nname = "${binding.name}"` + + `\nclass_name = "${binding.class_name}"` + + (binding.script_name ? `\nscript_name = "${binding.script_name}"` : "") + ); + + case "hyperdrive": + return ( + "[[hyperdrive]]" + + `\nbinding = "${binding.name}"` + + `\nid = "${binding.id}"` + ); + + case "kv_namespace": + return ( + "[[kv_namespaces]]" + + `\nbinding = "${binding.name}"` + + `\nid = "${binding.namespace_id}"` + ); + + case "mtls_certificate": + return ( + "[[mtls_certificates]]" + + `\nbinding = "${binding.name}"` + + `\ncertificate_id = "${binding.certificate_id}"` + ); + + case "queue": + return ( + "[[queues.producers]]" + + `\nbinding = "${binding.name}"` + + `\nqueue = "${binding.queue_name}"` + + (binding.delivery_delay + ? `\ndelivery_delay = ${binding.delivery_delay}` + : "") + ); + + case "r2_bucket": + return ( + "[[r2_buckets]]" + + `\nbinding = "${binding.name}"` + + `\nbucket_name = "${binding.bucket_name}"` + + (binding.jurisdiction + ? `\njurisdiction = "${binding.jurisdiction}"` + : "") + ); + + case "send_email": + return ( + "[[send_email]]" + + `\nname = "${binding.name}"` + + (binding.destination_address + ? `\ndestination_address = "${binding.destination_address}"` + : "") + + (binding.allowed_destination_addresses + ? `\nallowed_destination_addresses = [${binding.allowed_destination_addresses.map((addr) => `"${addr}"`).join(", ")}]` + : "") + ); + + case "service": + return ( + "[[services]]" + + `\nbinding = "${binding.name}"` + + `\nservice = "${binding.name}"` + + (binding.entrypoint ? `\nentrypoint = "${binding.entrypoint}"` : "") + ); + + case "vectorize": + return ( + "[[vectorize]]" + + `\nbinding = "${binding.name}"` + + `\nindex_name = "${binding.index_name}"` + ); + + case "version_metadata": + return "[version_metadata]" + `\nbinding = "${binding.name}"`; + + default: + return null; + } }