diff --git a/.changeset/hip-cameras-yawn.md b/.changeset/hip-cameras-yawn.md new file mode 100644 index 000000000000..45806ade96a8 --- /dev/null +++ b/.changeset/hip-cameras-yawn.md @@ -0,0 +1,15 @@ +--- +"wrangler": minor +--- + +feat: implement the `wrangler cert upload` command + +This command allows users to upload a mTLS certificate/private key or certificate-authority certificate chain. + +For uploading mTLS certificate, run: + +- `wrangler cert upload mtls-certificate --cert cert.pem --key key.pem --name MY_CERT` + +For uploading CA certificate chain, run: + +- `wrangler cert upload certificate-authority --ca-cert server-ca.pem --name SERVER_CA` diff --git a/packages/wrangler/e2e/cert.test.ts b/packages/wrangler/e2e/cert.test.ts new file mode 100644 index 000000000000..da66345cf061 --- /dev/null +++ b/packages/wrangler/e2e/cert.test.ts @@ -0,0 +1,199 @@ +import { randomUUID } from "node:crypto"; +import * as forge from "node-forge"; +import { describe, expect, it } from "vitest"; +import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test"; +import { normalizeOutput } from "./helpers/normalize"; + +// Generate X509 self signed root key pair and certificate +function generateRootCertificate() { + const rootKeys = forge.pki.rsa.generateKeyPair(2048); + const rootCert = forge.pki.createCertificate(); + rootCert.publicKey = rootKeys.publicKey; + rootCert.serialNumber = "01"; + rootCert.validity.notBefore = new Date(); + rootCert.validity.notAfter = new Date(); + rootCert.validity.notAfter.setFullYear( + rootCert.validity.notBefore.getFullYear() + 10 + ); // 10 years validity + + const rootAttrs = [ + { name: "commonName", value: "Root CA" }, + { name: "countryName", value: "US" }, + { shortName: "ST", value: "California" }, + { name: "organizationName", value: "Localhost Root CA" }, + ]; + rootCert.setSubject(rootAttrs); + rootCert.setIssuer(rootAttrs); // Self-signed + + rootCert.sign(rootKeys.privateKey, forge.md.sha256.create()); + + return { certificate: rootCert, privateKey: rootKeys.privateKey }; +} + +// Generate X509 leaf certificate signed by the root +function generateLeafCertificate( + rootCert: forge.pki.Certificate, + rootKey: forge.pki.PrivateKey +) { + const leafKeys = forge.pki.rsa.generateKeyPair(2048); + const leafCert = forge.pki.createCertificate(); + leafCert.publicKey = leafKeys.publicKey; + leafCert.serialNumber = "02"; + leafCert.validity.notBefore = new Date(); + leafCert.validity.notAfter = new Date(); + leafCert.validity.notAfter.setFullYear(2034, 10, 18); + + const leafAttrs = [ + { name: "commonName", value: "example.org" }, + { name: "countryName", value: "US" }, + { shortName: "ST", value: "California" }, + { name: "organizationName", value: "Example Inc" }, + ]; + leafCert.setSubject(leafAttrs); + leafCert.setIssuer(rootCert.subject.attributes); // Signed by root + + leafCert.sign(rootKey, forge.md.sha256.create()); // Signed using root's private key + + const pemLeafCert = forge.pki.certificateToPem(leafCert); + const pemLeafKey = forge.pki.privateKeyToPem(leafKeys.privateKey); + + return { certificate: pemLeafCert, privateKey: pemLeafKey }; +} + +// Generate self signed X509 CA root certificate +function generateRootCaCert() { + // Create a key pair (private and public keys) + const keyPair = forge.pki.rsa.generateKeyPair(2048); + + // Create a new X.509 certificate + const cert = forge.pki.createCertificate(); + + // Set certificate fields + cert.publicKey = keyPair.publicKey; + cert.serialNumber = "01"; + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(2034, 10, 18); + + // Add issuer and subject fields (for a root CA, they are the same) + const attrs = [ + { name: "commonName", value: "Localhost CA" }, + { name: "countryName", value: "US" }, + { shortName: "ST", value: "California" }, + { name: "localityName", value: "San Francisco" }, + { name: "organizationName", value: "Localhost" }, + { shortName: "OU", value: "SSL Department" }, + ]; + cert.setSubject(attrs); + cert.setIssuer(attrs); + + // Add basic constraints and key usage extensions + cert.setExtensions([ + { + name: "basicConstraints", + cA: true, + }, + { + name: "keyUsage", + keyCertSign: true, + digitalSignature: true, + cRLSign: true, + }, + ]); + + // Self-sign the certificate with the private key + cert.sign(keyPair.privateKey, forge.md.sha256.create()); + + // Convert the certificate and private key to PEM format + const pemCert = forge.pki.certificateToPem(cert); + const pemPrivateKey = forge.pki.privateKeyToPem(keyPair.privateKey); + + return { certificate: pemCert, privateKey: pemPrivateKey }; +} + +describe("cert", () => { + const normalize = (str: string) => + normalizeOutput(str, { + [process.env.CLOUDFLARE_ACCOUNT_ID as string]: "CLOUDFLARE_ACCOUNT_ID", + }); + const helper = new WranglerE2ETestHelper(); + // Generate root and leaf certificates + const { certificate: rootCert, privateKey: rootKey } = + generateRootCertificate(); + const { certificate: leafCert, privateKey: leafKey } = + generateLeafCertificate(rootCert, rootKey); + const { certificate: caCert } = generateRootCaCert(); + + // Generate filenames for concurrent e2e test environment + const mtlsCertName = `mtls_cert_${randomUUID()}`; + const caCertName = `ca_cert_${randomUUID()}`; + + it("upload mtls-certificate", async () => { + // locally generated certs/key + await helper.seed({ "mtls_client_cert_file.pem": leafCert }); + await helper.seed({ "mtls_client_private_key_file.pem": leafKey }); + + const output = await helper.run( + `wrangler cert upload mtls-certificate --name ${mtlsCertName} --cert mtls_client_cert_file.pem --key mtls_client_private_key_file.pem` + ); + expect(normalize(output.stdout)).toMatchInlineSnapshot(` + "Uploading mTLS Certificate mtls_cert_00000000-0000-0000-0000-000000000000... + Success! Uploaded mTLS Certificate mtls_cert_00000000-0000-0000-0000-000000000000 + ID: 00000000-0000-0000-0000-000000000000 + Issuer: CN=Root CA,O=Localhost Root CA,ST=California,C=US + Expires on 11/18/2034" + `); + }); + + it("upload certificate-authority", async () => { + await helper.seed({ "ca_chain_cert.pem": caCert }); + + const output = await helper.run( + `wrangler cert upload certificate-authority --name ${caCertName} --ca-cert ca_chain_cert.pem` + ); + expect(normalize(output.stdout)).toMatchInlineSnapshot(` + "Uploading CA Certificate ca_cert_00000000-0000-0000-0000-000000000000... + Success! Uploaded CA Certificate ca_cert_00000000-0000-0000-0000-000000000000 + ID: 00000000-0000-0000-0000-000000000000 + Issuer: CN=Localhost CA,OU=SSL Department,O=Localhost,L=San Francisco,ST=California,C=US + Expires on 11/18/2034" + `); + }); + + it("list cert", async () => { + const output = await helper.run(`wrangler cert list`); + const result = normalize(output.stdout); + expect(result).toContain( + `Name: mtls_cert_00000000-0000-0000-0000-000000000000` + ); + expect(result).toContain( + `Name: ca_cert_00000000-0000-0000-0000-000000000000` + ); + }); + + it("delete mtls cert", async () => { + const delete_mtls_cert_output = await helper.run( + `wrangler cert delete --name ${mtlsCertName}` + ); + expect(normalize(delete_mtls_cert_output.stdout)).toMatchInlineSnapshot( + ` + "? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (mtls_cert_00000000-0000-0000-0000-000000000000)? + 🤖 Using fallback value in non-interactive context: yes + Deleted certificate 00000000-0000-0000-0000-000000000000 (mtls_cert_00000000-0000-0000-0000-000000000000) successfully" + ` + ); + }); + + it("delete ca chain cert", async () => { + const delete_ca_cert_output = await helper.run( + `wrangler cert delete --name ${caCertName}` + ); + expect(normalize(delete_ca_cert_output.stdout)).toMatchInlineSnapshot( + ` + "? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (ca_cert_00000000-0000-0000-0000-000000000000)? + 🤖 Using fallback value in non-interactive context: yes + Deleted certificate 00000000-0000-0000-0000-000000000000 (ca_cert_00000000-0000-0000-0000-000000000000) successfully" + ` + ); + }); +}); diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 812385dc7f99..d31b73d77805 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -101,6 +101,7 @@ "@types/mime": "^3.0.4", "@types/minimatch": "^5.1.2", "@types/node": "catalog:default", + "@types/node-forge": "^1.3.11", "@types/prompts": "^2.0.14", "@types/resolve": "^1.20.6", "@types/shell-quote": "^1.7.2", @@ -133,6 +134,7 @@ "minimatch": "^5.1.0", "mock-socket": "^9.3.1", "msw": "2.4.3", + "node-forge": "^1.3.1", "open": "^8.4.0", "p-queue": "^7.2.0", "patch-console": "^1.0.0", diff --git a/packages/wrangler/src/__tests__/cert.test.ts b/packages/wrangler/src/__tests__/cert.test.ts new file mode 100644 index 000000000000..51aed259ddd4 --- /dev/null +++ b/packages/wrangler/src/__tests__/cert.test.ts @@ -0,0 +1,667 @@ +import { writeFileSync } from "fs"; +import { http, HttpResponse } from "msw"; +import { + deleteMTlsCertificate, + getMTlsCertificate, + getMTlsCertificateByName, + listMTlsCertificates, + uploadCaCertificateFromFs, + uploadMTlsCertificate, + uploadMTlsCertificateFromFs, +} from "../api"; +import { type MTlsCertificateResponse } from "../api/mtls-certificate"; +import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; +import { mockConsoleMethods } from "./helpers/mock-console"; +import { mockConfirm } from "./helpers/mock-dialogs"; +import { useMockIsTTY } from "./helpers/mock-istty"; +import { msw } from "./helpers/msw"; +import { runInTempDir } from "./helpers/run-in-tmp"; +import { runWrangler } from "./helpers/run-wrangler"; + +describe("wrangler", () => { + mockAccountId(); + mockApiToken(); + runInTempDir(); + const { setIsTTY } = useMockIsTTY(); + const std = mockConsoleMethods(); + + beforeEach(() => { + setIsTTY(true); + }); + + function mockPostMTlsCertificate( + resp: Partial = {} + ) { + const config = { calls: 0 }; + msw.use( + http.post( + "*/accounts/:accountId/mtls_certificates", + async ({ request }) => { + config.calls++; + + const body = (await request.json()) as Record; + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: { + id: "1234", + name: body.name, + certificates: body.certificates, + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + ...resp, + }, + }); + }, + { once: true } + ) + ); + return config; + } + + function mockPostCaChainCertificate( + resp: Partial = {} + ) { + const config = { calls: 0 }; + + msw.use( + http.post( + "*/accounts/:accountId/mtls_certificates", + async ({ request }) => { + config.calls++; + + const body = (await request.json()) as Record; + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: { + id: "1234", + name: body.name, + certificates: body.certificates, + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + ca: true, + ...resp, + }, + }); + }, + { once: true } + ) + ); + return config; + } + + function mockGetMTlsCertificates( + certs: Partial[] | undefined = undefined + ) { + const config = { calls: 0 }; + msw.use( + http.get( + "*/accounts/:accountId/mtls_certificates", + async () => { + config.calls++; + + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: + typeof certs === "undefined" + ? [ + { + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + { + id: "5678", + name: "cert two", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + ] + : certs, + }); + }, + { once: true } + ) + ); + return config; + } + + function mockGetMTlsCertificate(resp: Partial = {}) { + const config = { calls: 0 }; + msw.use( + http.get( + "*/accounts/:accountId/mtls_certificates/:certId", + async () => { + config.calls++; + + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: { + id: "1234", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + ...resp, + }, + }); + }, + { once: true } + ) + ); + return config; + } + + function mockDeleteMTlsCertificate() { + const config = { calls: 0 }; + msw.use( + http.delete( + "*/accounts/:accountId/mtls_certificates/:certId", + async () => { + config.calls++; + + return HttpResponse.json({ + success: true, + errors: [], + messages: [], + result: null, + }); + }, + { once: true } + ) + ); + return config; + } + + const now = new Date(); + const oneYearLater = new Date(now); + oneYearLater.setFullYear(now.getFullYear() + 1); + + describe("cert", () => { + describe("api", () => { + describe("uploadMTlsCertificate", () => { + it("should call mtls_certificates upload endpoint", async () => { + const mock = mockPostMTlsCertificate({ + id: "1234", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }); + + const cert = await uploadMTlsCertificate("some-account-id", { + certificateChain: "BEGIN CERTIFICATE...", + privateKey: "BEGIN PRIVATE KEY...", + name: "my_cert", + }); + + expect(cert.id).toEqual("1234"); + expect(cert.issuer).toEqual("example.com..."); + expect(cert.expires_on).toEqual(oneYearLater.toISOString()); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("uploadMTlsCertificateFromFs", () => { + it("should fail to read cert and key files when missing", async () => { + await expect( + uploadMTlsCertificateFromFs("some-account-id", { + certificateChainFilename: "cert.pem", + privateKeyFilename: "key.pem", + name: "my_cert", + }) + ).rejects.toMatchInlineSnapshot( + `[ParseError: Could not read file: cert.pem]` + ); + }); + + it("should read cert and key from disk and call mtls_certificates upload endpoint", async () => { + const mock = mockPostMTlsCertificate({ + id: "1234", + issuer: "example.com...", + }); + + writeFileSync("cert.pem", "BEGIN CERTIFICATE..."); + writeFileSync("key.pem", "BEGIN PRIVATE KEY..."); + + const cert = await uploadMTlsCertificateFromFs("some-account-id", { + certificateChainFilename: "cert.pem", + privateKeyFilename: "key.pem", + name: "my_cert", + }); + + expect(cert.id).toEqual("1234"); + expect(cert.issuer).toEqual("example.com..."); + expect(cert.expires_on).toEqual(oneYearLater.toISOString()); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("uploadCaCertificateFromFs", () => { + it("should fail to read ca cert when file is missing", async () => { + await expect( + uploadCaCertificateFromFs("some-account-id", { + certificates: "caCert.pem", + ca: true, + name: "my_cert", + }) + ).rejects.toMatchInlineSnapshot( + `[ParseError: Could not read file: caCert.pem]` + ); + }); + + it("should read ca cert from disk and call mtls_certificates upload endpoint", async () => { + const mock = mockPostCaChainCertificate({ + id: "1234", + issuer: "example.com...", + }); + + writeFileSync("caCert.pem", "BEGIN CERTIFICATE..."); + + const cert = await uploadCaCertificateFromFs("some-account-id", { + certificates: "caCert.pem", + ca: true, + name: "my_cert", + }); + + expect(cert.id).toEqual("1234"); + expect(cert.issuer).toEqual("example.com..."); + expect(cert.expires_on).toEqual(oneYearLater.toISOString()); + expect(cert.ca).toEqual(true); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("listMTlsCertificates", () => { + it("should call mtls_certificates list endpoint", async () => { + const mock = mockGetMTlsCertificates([ + { + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + { + id: "5678", + name: "cert two", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + ]); + + const certs = await listMTlsCertificates("some-account-id", {}, true); + + expect(certs).toHaveLength(2); + + expect(certs[0].id).toEqual("1234"); + expect(certs[0].name).toEqual("cert one"); + + expect(certs[1].id).toEqual("5678"); + expect(certs[1].name).toEqual("cert two"); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("getMTlsCertificate", () => { + it("calls get mtls_certificates endpoint", async () => { + const mock = mockGetMTlsCertificate({ + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }); + + const cert = await getMTlsCertificate("some-account-id", "1234"); + + expect(cert.id).toEqual("1234"); + expect(cert.issuer).toEqual("example.com..."); + expect(cert.expires_on).toEqual(oneYearLater.toISOString()); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("getMTlsCertificateByName", () => { + it("calls list mtls_certificates endpoint with name", async () => { + const mock = mockGetMTlsCertificates([ + { + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + ]); + + const cert = await getMTlsCertificateByName( + "some-account-id", + "cert one", + true + ); + + expect(cert.id).toEqual("1234"); + expect(cert.issuer).toEqual("example.com..."); + expect(cert.expires_on).toEqual(oneYearLater.toISOString()); + + expect(mock.calls).toEqual(1); + }); + + it("errors when a certificate cannot be found", async () => { + const mock = mockGetMTlsCertificates([]); + + await expect( + getMTlsCertificateByName("some-account-id", "cert one", true) + ).rejects.toMatchInlineSnapshot( + `[Error: certificate not found with name "cert one"]` + ); + + expect(mock.calls).toEqual(1); + }); + + it("errors when multiple certificates are found", async () => { + const mock = mockGetMTlsCertificates([ + { + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + { + id: "5678", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + ]); + + await expect( + getMTlsCertificateByName("some-account-id", "cert one", true) + ).rejects.toMatchInlineSnapshot( + `[Error: multiple certificates found with name "cert one"]` + ); + + expect(mock.calls).toEqual(1); + }); + }); + + describe("deleteMTlsCertificate", () => { + test("calls delete mts_certificates endpoint", async () => { + const mock = mockDeleteMTlsCertificate(); + + await deleteMTlsCertificate("some-account-id", "1234"); + + expect(mock.calls).toEqual(1); + }); + }); + }); + + describe("commands", () => { + describe("help", () => { + it("should show the correct help text", async () => { + await runWrangler("cert --help"); + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(` + "wrangler cert + + 🪪 Manage client mTLS certificates and CA certificate chains used for secured connections [open-beta] + + COMMANDS + wrangler cert upload Upload a new cert [open-beta] + wrangler cert list List uploaded mTLS certificates + wrangler cert delete Delete an mTLS certificate + + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + -e, --env Environment to use for operations and .env files [string] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" + `); + }); + }); + + describe("upload", () => { + test("uploads certificate and key from file", async () => { + writeFileSync("cert.pem", "BEGIN CERTIFICATE..."); + writeFileSync("key.pem", "BEGIN PRIVATE KEY..."); + + mockPostMTlsCertificate(); + + await runWrangler( + "cert upload mtls-certificate --cert cert.pem --key key.pem" + ); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toEqual( + `Uploading mTLS Certificate... +Success! Uploaded mTLS Certificate +ID: 1234 +Issuer: example.com... +Expires on ${oneYearLater.toLocaleDateString()}` + ); + }); + + test("uploads certificate and key from file with name", async () => { + writeFileSync("cert.pem", "BEGIN CERTIFICATE..."); + writeFileSync("key.pem", "BEGIN PRIVATE KEY..."); + + mockPostMTlsCertificate(); + + await runWrangler( + "cert upload mtls-certificate --cert cert.pem --key key.pem --name my-cert" + ); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toEqual( + `Uploading mTLS Certificate my-cert... +Success! Uploaded mTLS Certificate my-cert +ID: 1234 +Issuer: example.com... +Expires on ${oneYearLater.toLocaleDateString()}` + ); + }); + + test("uploads ca certificate chain from file", async () => { + writeFileSync("caCert.pem", "BEGIN CERTIFICATE..."); + + mockPostCaChainCertificate(); + + await runWrangler( + "cert upload certificate-authority --ca-cert caCert.pem" + ); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toEqual( + `Uploading CA Certificate... +Success! Uploaded CA Certificate +ID: 1234 +Issuer: example.com... +Expires on ${oneYearLater.toLocaleDateString()}` + ); + }); + + test("uploads ca certificate chain from file with name", async () => { + writeFileSync("caCert.pem", "BEGIN CERTIFICATE..."); + + mockPostCaChainCertificate(); + + await runWrangler( + "cert upload certificate-authority --ca-cert caCert.pem --name my-caCert" + ); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toEqual( + `Uploading CA Certificate my-caCert... +Success! Uploaded CA Certificate my-caCert +ID: 1234 +Issuer: example.com... +Expires on ${oneYearLater.toLocaleDateString()}` + ); + }); + }); + + describe("list", () => { + it("should list certificates", async () => { + mockGetMTlsCertificates(); + + await runWrangler("cert list"); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toEqual( + `ID: 1234 +Name: cert one +Issuer: example.com... +Created on: ${now.toLocaleDateString()} +Expires on: ${oneYearLater.toLocaleDateString()} + + +ID: 5678 +Name: cert two +Issuer: example.com... +Created on: ${now.toLocaleDateString()} +Expires on: ${oneYearLater.toLocaleDateString()} + +` + ); + }); + }); + + describe("delete", () => { + it("should require --id or --name", async () => { + await runWrangler("cert delete"); + + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Must provide --id or --name. + + " + `); + expect(std.out).toMatchInlineSnapshot(`""`); + }); + + it("should require not providing --id and --name", async () => { + await runWrangler("cert delete --id 1234 --name mycert"); + + expect(std.err).toMatchInlineSnapshot(` + "X [ERROR] Can't provide both --id and --name. + + " + `); + expect(std.out).toMatchInlineSnapshot(`""`); + }); + + it("should delete certificate by id", async () => { + mockGetMTlsCertificate({ name: "my-cert" }); + mockDeleteMTlsCertificate(); + + mockConfirm({ + text: `Are you sure you want to delete certificate 1234 (my-cert)?`, + result: true, + }); + + await runWrangler("cert delete --id 1234"); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot( + `"Deleted certificate 1234 (my-cert) successfully"` + ); + }); + + it("should delete certificate by name", async () => { + mockGetMTlsCertificates([{ id: "1234", name: "my-cert" }]); + mockDeleteMTlsCertificate(); + + mockConfirm({ + text: `Are you sure you want to delete certificate 1234 (my-cert)?`, + result: true, + }); + + await runWrangler("cert delete --name my-cert"); + + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot( + `"Deleted certificate 1234 (my-cert) successfully"` + ); + }); + + it("should not delete when certificate cannot be found by name", async () => { + mockGetMTlsCertificates([]); + + await expect( + runWrangler("cert delete --name my-cert") + ).rejects.toMatchInlineSnapshot( + `[Error: certificate not found with name "my-cert"]` + ); + expect(std.out).toMatchInlineSnapshot(`""`); + }); + + it("should not delete when many certificates are found by name", async () => { + mockGetMTlsCertificates([ + { + id: "1234", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + { + id: "5678", + name: "cert one", + certificates: "BEGIN CERTIFICATE...", + issuer: "example.com...", + uploaded_on: now.toISOString(), + expires_on: oneYearLater.toISOString(), + }, + ]); + + await expect( + runWrangler("cert delete --name my-cert") + ).rejects.toMatchInlineSnapshot( + `[Error: multiple certificates found with name "my-cert"]` + ); + expect(std.out).toMatchInlineSnapshot(`""`); + }); + + it("should not delete when confirmation fails", async () => { + mockGetMTlsCertificate({ id: "1234" }); + + mockConfirm({ + text: `Are you sure you want to delete certificate 1234?`, + result: false, + }); + + await runWrangler("cert delete --id 1234"); + expect(std.err).toMatchInlineSnapshot(`""`); + expect(std.out).toMatchInlineSnapshot(`"Not deleting"`); + }); + }); + }); + }); +}); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index aec9c93f1a85..b6672e328bf0 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -56,6 +56,7 @@ describe("wrangler", () => { wrangler d1 🗄 Manage Workers D1 databases wrangler vectorize 🧮 Manage Vectorize indexes [open beta] wrangler hyperdrive 🚀 Manage Hyperdrive databases + wrangler cert 🪪 Manage client mTLS certificates and CA certificate chains used for secured connections [open-beta] wrangler pages ⚡️ Configure Cloudflare Pages wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler pubsub 📮 Manage Pub/Sub brokers [private beta] @@ -112,6 +113,7 @@ describe("wrangler", () => { wrangler d1 🗄 Manage Workers D1 databases wrangler vectorize 🧮 Manage Vectorize indexes [open beta] wrangler hyperdrive 🚀 Manage Hyperdrive databases + wrangler cert 🪪 Manage client mTLS certificates and CA certificate chains used for secured connections [open-beta] wrangler pages ⚡️ Configure Cloudflare Pages wrangler mtls-certificate 🪪 Manage certificates used for mTLS connections wrangler pubsub 📮 Manage Pub/Sub brokers [private beta] diff --git a/packages/wrangler/src/api/index.ts b/packages/wrangler/src/api/index.ts index 19ae282f1afc..1c629f36f529 100644 --- a/packages/wrangler/src/api/index.ts +++ b/packages/wrangler/src/api/index.ts @@ -8,6 +8,7 @@ export { getMTlsCertificate, getMTlsCertificateByName, deleteMTlsCertificate, + uploadCaCertificateFromFs, } from "./mtls-certificate"; export * from "./startDevWorker"; export * from "./integrations"; diff --git a/packages/wrangler/src/api/mtls-certificate.ts b/packages/wrangler/src/api/mtls-certificate.ts index 4c904c535bb8..91dda639a076 100644 --- a/packages/wrangler/src/api/mtls-certificate.ts +++ b/packages/wrangler/src/api/mtls-certificate.ts @@ -35,6 +35,15 @@ export interface MTlsCertificateBody { name?: string; } +/** + * details for uploading an CA certificate or CA chain via the ssl api + */ +export interface CaCertificateBody { + certificates: string; + ca: boolean; + name?: string; +} + /** * supported filters for listing mTLS certificates via the ssl api */ @@ -66,6 +75,23 @@ export async function uploadMTlsCertificateFromFs( }); } +/** + * reads an CA certificate from disk and uploads it to the account mTLS certificate store + */ +export async function uploadCaCertificateFromFs( + accountId: string, + details: CaCertificateBody +): Promise { + return await fetchResult(`/accounts/${accountId}/mtls_certificates`, { + method: "POST", + body: JSON.stringify({ + name: details.name, + certificates: readFileSync(details.certificates), + ca: details.ca, + }), + }); +} + /** * uploads an mTLS certificate and private key pair to the account mTLS certificate store */ @@ -102,10 +128,13 @@ export async function getMTlsCertificate( */ export async function listMTlsCertificates( accountId: string, - filter: MTlsCertificateListFilter + filter: MTlsCertificateListFilter, + ca: boolean = false ): Promise { const params = new URLSearchParams(); - params.append("ca", "false"); + if (!ca) { + params.append("ca", String(false)); + } if (filter.name) { params.append("name", filter.name); } @@ -121,9 +150,10 @@ export async function listMTlsCertificates( */ export async function getMTlsCertificateByName( accountId: string, - name: string + name: string, + ca: boolean = false ): Promise { - const certificates = await listMTlsCertificates(accountId, { name }); + const certificates = await listMTlsCertificates(accountId, { name }, ca); if (certificates.length === 0) { throw new ErrorMTlsCertificateNameNotFound( `certificate not found with name "${name}"` diff --git a/packages/wrangler/src/cert/cert.ts b/packages/wrangler/src/cert/cert.ts new file mode 100644 index 000000000000..2d825328c57c --- /dev/null +++ b/packages/wrangler/src/cert/cert.ts @@ -0,0 +1,204 @@ +import { + deleteMTlsCertificate, + getMTlsCertificate, + getMTlsCertificateByName, + listMTlsCertificates, + uploadCaCertificateFromFs, + uploadMTlsCertificateFromFs, +} from "../api/mtls-certificate"; +import { createCommand, createNamespace } from "../core/create-command"; +import { confirm } from "../dialogs"; +import { logger } from "../logger"; +import { requireAuth } from "../user"; +import type { MTlsCertificateResponse } from "../api/mtls-certificate"; + +// wrangler cert +export const certNamespace = createNamespace({ + metadata: { + description: + "🪪 Manage client mTLS certificates and CA certificate chains used for secured connections", + status: "open-beta", + owner: "Product: SSL", + }, +}); + +export const certUploadNamespace = createNamespace({ + metadata: { + description: "Upload a new cert", + status: "open-beta", + owner: "Product: SSL", + }, +}); + +// wrangler cert upload mtls-certificate +export const certUploadMtlsCommand = createCommand({ + metadata: { + description: "Upload an mTLS certificate", + status: "stable", + owner: "Product: SSL", + }, + args: { + cert: { + description: + "The path to a certificate file (.pem) containing a chain of certificates to upload", + type: "string", + demandOption: true, + }, + key: { + description: + "The path to a file containing the private key for your leaf certificate", + type: "string", + demandOption: true, + }, + name: { + description: "The name for the certificate", + type: "string", + }, + }, + async handler({ cert, key, name }, { config }) { + const accountId = await requireAuth(config); + logger.log( + name + ? `Uploading mTLS Certificate ${name}...` + : `Uploading mTLS Certificate...` + ); + const certResponse = await uploadMTlsCertificateFromFs(accountId, { + certificateChainFilename: cert, + privateKeyFilename: key, + name, + }); + const expiresOn = new Date(certResponse.expires_on).toLocaleDateString(); + logger.log( + name + ? `Success! Uploaded mTLS Certificate ${name}` + : `Success! Uploaded mTLS Certificate` + ); + logger.log(`ID: ${certResponse.id}`); + logger.log(`Issuer: ${certResponse.issuer}`); + logger.log(`Expires on ${expiresOn}`); + }, +}); + +// wrangler cert upload ca +export const certUploadCaCertCommand = createCommand({ + metadata: { + description: "Upload a CA certificate chain", + status: "stable", + owner: "Product: SSL", + }, + args: { + name: { + describe: "The name for the certificate", + type: "string", + }, + "ca-cert": { + description: + "The path to a certificate file (.pem) containing a chain of CA certificates to upload", + type: "string", + demandOption: true, + }, + }, + async handler({ caCert, name }, { config }) { + const accountId = await requireAuth(config); + logger.log( + name + ? `Uploading CA Certificate ${name}...` + : `Uploading CA Certificate...` + ); + const certResponse = await uploadCaCertificateFromFs(accountId, { + certificates: caCert, + ca: true, + name, + }); + const expiresOn = new Date(certResponse.expires_on).toLocaleDateString(); + logger.log( + name + ? `Success! Uploaded CA Certificate ${name}` + : `Success! Uploaded CA Certificate` + ); + logger.log(`ID: ${certResponse.id}`); + logger.log(`Issuer: ${certResponse.issuer}`); + logger.log(`Expires on ${expiresOn}`); + }, +}); + +// wrangler cert list +export const certListCommand = createCommand({ + metadata: { + description: "List uploaded mTLS certificates", + status: "stable", + owner: "Product: SSL", + }, + async handler(_, { config }) { + const accountId = await requireAuth(config); + const certificates = await listMTlsCertificates(accountId, {}, true); + for (const certificate of certificates) { + logger.log(`ID: ${certificate.id}`); + if (certificate.name) { + logger.log(`Name: ${certificate.name}`); + } + logger.log(`Issuer: ${certificate.issuer}`); + logger.log( + `Created on: ${new Date(certificate.uploaded_on).toLocaleDateString()}` + ); + logger.log( + `Expires on: ${new Date(certificate.expires_on).toLocaleDateString()}` + ); + if (certificate.ca) { + logger.log(`CA: ${certificate.ca}`); + } + logger.log("\n"); + } + }, +}); + +// wrangler cert delete +export const certDeleteCommand = createCommand({ + metadata: { + description: "Delete an mTLS certificate", + status: "stable", + owner: "Product: SSL", + }, + args: { + id: { + description: "The id of the mTLS certificate to delete", + type: "string", + }, + name: { + description: "The name of the mTLS certificate record to delete", + type: "string", + }, + }, + async handler({ id, name }, { config }) { + const accountId = await requireAuth(config); + if (id && name) { + return logger.error(`Can't provide both --id and --name.`); + } else if (!id && !name) { + return logger.error(`Must provide --id or --name.`); + } + let certificate: MTlsCertificateResponse; + if (id) { + certificate = await getMTlsCertificate(accountId, id); + } else { + certificate = await getMTlsCertificateByName( + accountId, + name as string, + true + ); + } + + const response = await confirm( + `Are you sure you want to delete certificate ${certificate.id}${certificate.name ? ` (${certificate.name})` : ""}?` + ); + if (!response) { + logger.log("Not deleting"); + return; + } + + await deleteMTlsCertificate(accountId, certificate.id); + + logger.log( + `Deleted certificate ${certificate.id} ${certificate.name ? `(${certificate.name})` : ""} successfully` + ); + }, +}); diff --git a/packages/wrangler/src/core/teams.d.ts b/packages/wrangler/src/core/teams.d.ts index 204e2312f745..ccdab8697572 100644 --- a/packages/wrangler/src/core/teams.d.ts +++ b/packages/wrangler/src/core/teams.d.ts @@ -15,4 +15,5 @@ export type Teams = | "Product: Hyperdrive" | "Product: Vectorize" | "Product: Workflows" - | "Product: Cloudchamber"; + | "Product: Cloudchamber" + | "Product: SSL"; diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index ac6697642f29..a0ee7f77fd5e 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -5,6 +5,14 @@ import { ProxyAgent, setGlobalDispatcher } from "undici"; import makeCLI from "yargs"; import { version as wranglerVersion } from "../package.json"; import { ai } from "./ai"; +import { + certDeleteCommand, + certListCommand, + certNamespace, + certUploadCaCertCommand, + certUploadMtlsCommand, + certUploadNamespace, +} from "./cert/cert"; import { cloudchamber } from "./cloudchamber"; import { experimental_readRawConfig, loadDotEnv } from "./config"; import { demandSingleValue } from "./core"; @@ -701,6 +709,23 @@ export function createCLIParser(argv: string[]) { } ); + // cert - includes mtls-certificates and CA cert management + registry.define([ + { command: "wrangler cert", definition: certNamespace }, + { command: "wrangler cert upload", definition: certUploadNamespace }, + { + command: "wrangler cert upload mtls-certificate", + definition: certUploadMtlsCommand, + }, + { + command: "wrangler cert upload certificate-authority", + definition: certUploadCaCertCommand, + }, + { command: "wrangler cert list", definition: certListCommand }, + { command: "wrangler cert delete", definition: certDeleteCommand }, + ]); + registry.registerNamespace("cert"); + // pages wrangler.command("pages", "⚡️ Configure Cloudflare Pages", (pagesYargs) => { // Pages does not support the `--config`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb8879444a97..e011a0d71a8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: version: 5.0.12(@types/node@18.19.59) vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) fixtures/additional-modules: devDependencies: @@ -153,7 +153,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -171,7 +171,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -198,7 +198,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -219,7 +219,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -237,7 +237,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -258,7 +258,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -282,7 +282,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -306,7 +306,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) fixtures/isomorphic-random-example: {} @@ -331,7 +331,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -343,7 +343,7 @@ importers: version: 7.0.0 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -368,7 +368,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -386,7 +386,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -413,7 +413,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -431,7 +431,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -452,7 +452,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -480,7 +480,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -498,7 +498,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -516,7 +516,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -537,7 +537,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -555,7 +555,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -592,7 +592,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -613,7 +613,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -628,7 +628,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -649,7 +649,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -667,7 +667,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -685,7 +685,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -703,7 +703,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -721,7 +721,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -739,7 +739,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -757,7 +757,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -775,7 +775,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -796,7 +796,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -811,7 +811,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -836,7 +836,7 @@ importers: devDependencies: vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -878,7 +878,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -905,7 +905,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -937,7 +937,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -958,7 +958,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -979,7 +979,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -997,7 +997,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1015,7 +1015,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1234,7 +1234,7 @@ importers: version: 4.2.0(typescript@5.6.3)(vite@5.0.12(@types/node@18.19.59)) vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) which-pm-runs: specifier: ^1.1.0 version: 1.1.0 @@ -1689,7 +1689,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../wrangler @@ -2203,7 +2203,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) packages/workers-editor-shared: dependencies: @@ -2413,7 +2413,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) packages/workers-tsconfig: {} @@ -2436,7 +2436,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: specifier: workspace:* version: link:../wrangler @@ -2479,7 +2479,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) packages/wrangler: dependencies: @@ -2578,6 +2578,9 @@ importers: '@types/node': specifier: ^18.19.59 version: 18.19.59 + '@types/node-forge': + specifier: ^1.3.11 + version: 1.3.11 '@types/prompts': specifier: ^2.0.14 version: 2.0.14 @@ -2674,6 +2677,9 @@ importers: msw: specifier: 2.4.3 version: 2.4.3(typescript@5.6.3) + node-forge: + specifier: ^1.3.1 + version: 1.3.1 open: specifier: ^8.4.0 version: 8.4.0 @@ -5241,6 +5247,9 @@ packages: '@types/node-fetch@2.6.11': resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + '@types/node-forge@1.3.11': + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + '@types/node-polyglot@2.4.2': resolution: {integrity: sha512-Tfx3TU/PBK8vW/BG1TK793EHlVpGnoHUj+DGxOwNOYwZiueLeu7FgksvDdpEyFSw4+AKKiEuiMm8EGUHUR4o6g==} @@ -8417,8 +8426,8 @@ packages: encoding: optional: true - node-forge@1.3.0: - resolution: {integrity: sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==} + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} node-gyp-build@4.8.0: @@ -11532,7 +11541,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -11622,7 +11631,7 @@ snapshots: '@babel/parser': 7.26.3 '@babel/template': 7.25.9 '@babel/types': 7.26.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12084,7 +12093,7 @@ snapshots: esbuild: 0.17.19 miniflare: 3.20241106.1 semver: 7.6.3 - vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8) wrangler: 3.90.0(@cloudflare/workers-types@4.20241230.0) zod: 3.22.3 transitivePeerDependencies: @@ -12517,7 +12526,7 @@ snapshots: '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -12564,7 +12573,7 @@ snapshots: '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -12712,7 +12721,7 @@ snapshots: '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)': dependencies: detect-libc: 2.0.2 - https-proxy-agent: 5.0.1(supports-color@9.2.2) + https-proxy-agent: 5.0.1 make-dir: 3.1.0 node-fetch: 2.6.11(encoding@0.1.13) nopt: 5.0.0 @@ -13867,6 +13876,10 @@ snapshots: '@types/node': 18.19.59 form-data: 4.0.0 + '@types/node-forge@1.3.11': + dependencies: + '@types/node': 18.19.59 + '@types/node-polyglot@2.4.2': {} '@types/node@18.19.59': @@ -13985,7 +13998,7 @@ snapshots: '@typescript-eslint/type-utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -14005,7 +14018,7 @@ snapshots: '@typescript-eslint/type-utils': 6.10.0(eslint@8.57.0)(typescript@5.7.3) '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.7.3) '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -14041,7 +14054,7 @@ snapshots: '@typescript-eslint/types': 6.10.0 '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.6.3 @@ -14054,7 +14067,7 @@ snapshots: '@typescript-eslint/types': 6.10.0 '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.7.3) '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.7.3 @@ -14067,7 +14080,7 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 optionalDependencies: typescript: 5.6.3 @@ -14088,7 +14101,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.6.3) '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.4.3(typescript@5.6.3) optionalDependencies: @@ -14100,7 +14113,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.7.3) '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.7.3) - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: @@ -14112,7 +14125,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.6.3) - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.0 ts-api-utils: 1.4.3(typescript@5.6.3) optionalDependencies: @@ -14128,7 +14141,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.10.0 '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 @@ -14142,7 +14155,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.10.0 '@typescript-eslint/visitor-keys': 6.10.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 @@ -14156,7 +14169,7 @@ snapshots: dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -14463,6 +14476,12 @@ snapshots: acorn@8.14.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + agent-base@6.0.2(supports-color@9.2.2): dependencies: debug: 4.3.7(supports-color@9.2.2) @@ -14714,7 +14733,7 @@ snapshots: common-path-prefix: 3.0.0 concordance: 5.0.4 currently-unhandled: 0.4.1 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) emittery: 1.0.1 figures: 6.0.1 globby: 14.0.1 @@ -14931,7 +14950,7 @@ snapshots: capnp-ts@0.5.1: dependencies: - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) format: 0.2.2 tslib: 2.8.1 utf8-encoding: 0.1.2 @@ -14940,7 +14959,7 @@ snapshots: capnp-ts@0.7.0(patch_hash=l4yimnxyvkiyj6alnps2ec3sii): dependencies: - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -14948,7 +14967,7 @@ snapshots: capnpc-ts@0.7.0: dependencies: capnp-ts: 0.5.1 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) hex2dec: 1.1.2 mkdirp: 1.0.4 tslib: 2.8.1 @@ -15758,7 +15777,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.17.19): dependencies: - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.17.19 transitivePeerDependencies: - supports-color @@ -16042,7 +16061,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -16449,7 +16468,7 @@ snapshots: dependencies: basic-ftp: 5.0.3 data-uri-to-buffer: 5.0.1 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -16660,7 +16679,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16669,6 +16688,13 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + https-proxy-agent@5.0.1(supports-color@9.2.2): dependencies: agent-base: 6.0.2(supports-color@9.2.2) @@ -16676,6 +16702,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + https-proxy-agent@7.0.2(supports-color@9.2.2): dependencies: agent-base: 7.1.3 @@ -16686,7 +16719,7 @@ snapshots: https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -17619,7 +17652,7 @@ snapshots: optionalDependencies: encoding: 0.1.13 - node-forge@1.3.0: {} + node-forge@1.3.1: {} node-gyp-build@4.8.0: {} @@ -17886,10 +17919,10 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) get-uri: 6.0.1 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.2(supports-color@9.2.2) + https-proxy-agent: 7.0.2 pac-resolver: 7.0.0 socks-proxy-agent: 8.0.2 transitivePeerDependencies: @@ -18424,9 +18457,9 @@ snapshots: proxy-agent@6.3.1: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.2(supports-color@9.2.2) + https-proxy-agent: 7.0.2 lru-cache: 7.18.3 pac-proxy-agent: 7.0.1 proxy-from-env: 1.1.0 @@ -18863,7 +18896,7 @@ snapshots: selfsigned@2.1.1: dependencies: - node-forge: 1.3.0 + node-forge: 1.3.1 sembear@0.5.2: dependencies: @@ -19011,7 +19044,7 @@ snapshots: socks-proxy-agent@8.0.2: dependencies: agent-base: 7.1.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -19468,7 +19501,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.1 consola: 3.3.3 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.24.2 joycon: 3.1.1 picocolors: 1.1.1 @@ -19822,6 +19855,23 @@ snapshots: dependencies: builtins: 5.0.1 + vite-node@2.1.8(@types/node@18.19.59): + dependencies: + cac: 6.7.14 + debug: 4.3.7(supports-color@8.1.1) + es-module-lexer: 1.5.4 + pathe: 1.1.2 + vite: 5.0.12(@types/node@18.19.59) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite-node@2.1.8(@types/node@18.19.59)(supports-color@9.2.2): dependencies: cac: 6.7.14 @@ -19846,7 +19896,7 @@ snapshots: '@volar/typescript': 2.3.4 '@vue/language-core': 2.0.29(typescript@5.7.3) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.17 @@ -19861,7 +19911,7 @@ snapshots: vite-tsconfig-paths@4.2.0(typescript@5.6.3)(vite@5.0.12(@types/node@18.19.59)): dependencies: - debug: 4.3.7(supports-color@9.2.2) + debug: 4.3.7(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 2.1.1(typescript@5.6.3) optionalDependencies: @@ -19895,6 +19945,41 @@ snapshots: mock-socket: 9.3.1 vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + vitest@2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(msw@2.4.3(typescript@5.6.3))(vite@5.0.12(@types/node@18.19.59)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7(supports-color@8.1.1) + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.0.12(@types/node@18.19.59) + vite-node: 2.1.8(@types/node@18.19.59) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 18.19.59 + '@vitest/ui': 2.1.8(vitest@2.1.8) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - stylus + - sugarss + - supports-color + - terser + vitest@2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2): dependencies: '@vitest/expect': 2.1.8