From 8224a3f8c966fcd93e3e671377b0b3f54d5e8523 Mon Sep 17 00:00:00 2001 From: yamachi4416 Date: Sat, 15 Nov 2025 09:54:19 +0900 Subject: [PATCH 1/3] fix(dev): respect https.xxx commandline arguments --- packages/nuxi/src/commands/dev.ts | 32 +++++++------ packages/nuxi/src/dev/utils.ts | 16 +++---- packages/nuxt-cli/test/e2e/dev.spec.ts | 62 ++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 22 deletions(-) diff --git a/packages/nuxi/src/commands/dev.ts b/packages/nuxi/src/commands/dev.ts index 1eedcade4..5167625a3 100644 --- a/packages/nuxi/src/commands/dev.ts +++ b/packages/nuxi/src/commands/dev.ts @@ -5,7 +5,7 @@ import process from 'node:process' import { defineCommand } from 'citty' import { colors } from 'consola/utils' -import { getArgs as getListhenArgs } from 'listhen/cli' +import { getArgs as getListhenArgs, parseArgs as parseListhenArgs } from 'listhen/cli' import { resolve } from 'pathe' import { satisfies } from 'semver' import { isBun, isTest } from 'std-env' @@ -46,26 +46,22 @@ const command = defineCommand({ }, ...{ ...listhenArgs, - 'port': { + port: { ...listhenArgs.port, description: 'Port to listen on (default: `NUXT_PORT || NITRO_PORT || PORT || nuxtOptions.devServer.port`)', alias: ['p'], }, - 'open': { + open: { ...listhenArgs.open, alias: ['o'], default: false, }, - 'host': { + host: { ...listhenArgs.host, alias: ['h'], description: 'Host to listen on (default: `NUXT_HOST || NITRO_HOST || HOST || nuxtOptions.devServer?.host`)', }, - 'clipboard': { ...listhenArgs.clipboard, default: false }, - 'https.domains': { - ...listhenArgs['https.domains'], - description: 'Comma separated list of domains and IPs, the autogenerated certificate should be valid for (https: true)', - }, + clipboard: { ...listhenArgs.clipboard, default: false }, }, sslCert: { type: 'string', @@ -186,11 +182,21 @@ function resolveListenOverrides(args: ParsedArgs) { || process.env.NUXT_SSL_KEY || process.env.NITRO_SSL_KEY - return { + const options = parseListhenArgs({ ...args, - 'open': (args.o as boolean) || args.open, - 'https.cert': _httpsCert || '', - 'https.key': _httpsKey || '', + 'https': args.https !== false, + 'https.cert': _httpsCert!, + 'https.key': _httpsKey!, + }) + + return { + ...options, + // if the https flag is not present, https.xxx arguments are ignored. + // override if https is enabled in devServer config. + _https: args.https, + get https(): typeof options['https'] { + return this._https ? options.https : false + }, } as const } diff --git a/packages/nuxi/src/dev/utils.ts b/packages/nuxi/src/dev/utils.ts index 31e740b60..fc2b2f79b 100644 --- a/packages/nuxi/src/dev/utils.ts +++ b/packages/nuxi/src/dev/utils.ts @@ -228,7 +228,7 @@ export class NuxtDevServer extends EventEmitter { if (urls) { // Pass hostname and https info for proper CORS and allowedHosts setup const overrides = this.options.listenOverrides || {} - const hostname = overrides.hostname ?? (overrides as any).host + const hostname = overrides.hostname const https = overrides.https loadOptions.defaults = resolveDevServerDefaults({ hostname, https }, urls) @@ -276,9 +276,7 @@ export class NuxtDevServer extends EventEmitter { const port = overrides.port ?? nuxtConfig.devServer?.port - // CLI args use 'host', but ListenOptions uses 'hostname' - const rawHost = overrides.hostname ?? (overrides as any).host - const hostname = (rawHost === true ? '' : rawHost) ?? nuxtConfig.devServer?.host + const hostname = overrides.hostname ?? nuxtConfig.devServer?.host // Resolve public flag const isPublic = provider === 'codesandbox' || (overrides.public ?? (isPublicHostname(hostname) ? true : undefined)) @@ -288,12 +286,12 @@ export class NuxtDevServer extends EventEmitter { ? nuxtConfig.devServer.https : {} - const httpsEnabled = !!(overrides.https ?? nuxtConfig.devServer?.https) + ;(overrides as any)._https ??= !!nuxtConfig.devServer?.https - const httpsOptions = httpsEnabled && { - ...httpsFromConfig, - ...(typeof overrides.https === 'object' ? overrides.https : {}), - } + const httpsOptions = overrides.https && defu( + (typeof overrides.https === 'object' ? overrides.https : {}), + httpsFromConfig, + ) // Resolve baseURL const baseURL = nuxtConfig.app?.baseURL?.startsWith?.('./') diff --git a/packages/nuxt-cli/test/e2e/dev.spec.ts b/packages/nuxt-cli/test/e2e/dev.spec.ts index 0b7ca2cef..4c8388c4c 100644 --- a/packages/nuxt-cli/test/e2e/dev.spec.ts +++ b/packages/nuxt-cli/test/e2e/dev.spec.ts @@ -92,4 +92,66 @@ describe('dev server', () => { await close() } }) + + it('should expose dev server https options to nuxt options', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3034 }) + const { result: { close } } = await runCommand('dev', [ + `--https`, + `--https.passphrase=pass`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + https: { passphrase: 'pass' }, + host, + port, + url: `https://${host}:${port}/`, + }) + }) + + it('should override devServer options with commandline options', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3050 }) + const { result: { close } } = await runCommand('dev', [ + `--https=false`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + devServer: { + host: 'localhost', + port: 3000, + https: { + cert: 'invalid', + key: 'invalid', + passphrase: 'dev', + }, + }, + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + https: false, + host, + port, + url: `http://${host}:${port}/`, + }) + }) }) From 45da4c8e9b5557c76c84a9a827364d8cea37a9ec Mon Sep 17 00:00:00 2001 From: yamachi4416 Date: Sun, 16 Nov 2025 12:25:36 +0900 Subject: [PATCH 2/3] fix(dev): respect HOST and PORT environment variables --- packages/nuxi/src/commands/dev.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/nuxi/src/commands/dev.ts b/packages/nuxi/src/commands/dev.ts index 5167625a3..9f8d528cd 100644 --- a/packages/nuxi/src/commands/dev.ts +++ b/packages/nuxi/src/commands/dev.ts @@ -172,21 +172,25 @@ function resolveListenOverrides(args: ParsedArgs) { } as const } - const _httpsCert = args['https.cert'] - || args.sslCert - || process.env.NUXT_SSL_CERT - || process.env.NITRO_SSL_CERT - - const _httpsKey = args['https.key'] - || args.sslKey - || process.env.NUXT_SSL_KEY - || process.env.NITRO_SSL_KEY - const options = parseListhenArgs({ ...args, + 'host': args.host + || process.env.NUXT_HOST + || process.env.NITRO_HOST + || process.env.HOST!, + 'port': args.port + || process.env.NUXT_PORT + || process.env.NITRO_PORT + || process.env.PORT!, 'https': args.https !== false, - 'https.cert': _httpsCert!, - 'https.key': _httpsKey!, + 'https.cert': args['https.cert'] + || args.sslCert + || process.env.NUXT_SSL_CERT + || process.env.NITRO_SSL_CERT!, + 'https.key': args['https.key'] + || args.sslKey + || process.env.NUXT_SSL_KEY + || process.env.NITRO_SSL_KEY!, }) return { From d963b66b751d79f3e14f0baacaec6e0ad5bdc777 Mon Sep 17 00:00:00 2001 From: yamachi4416 Date: Sun, 16 Nov 2025 12:27:09 +0900 Subject: [PATCH 3/3] chore(dev): add https and environment e2e test --- packages/nuxt-cli/test/e2e/dev.spec.ts | 287 ++++++++++++++++++++----- playground/certs/cert.dummy | 23 ++ playground/certs/key.dummy | 27 +++ playground/certs/pfx.dummy | Bin 0 -> 2589 bytes playground/package.json | 2 + 5 files changed, 283 insertions(+), 56 deletions(-) create mode 100644 playground/certs/cert.dummy create mode 100644 playground/certs/key.dummy create mode 100644 playground/certs/pfx.dummy diff --git a/packages/nuxt-cli/test/e2e/dev.spec.ts b/packages/nuxt-cli/test/e2e/dev.spec.ts index 4c8388c4c..e96e63c68 100644 --- a/packages/nuxt-cli/test/e2e/dev.spec.ts +++ b/packages/nuxt-cli/test/e2e/dev.spec.ts @@ -2,12 +2,21 @@ import { readFile, rm } from 'node:fs/promises' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { getPort } from 'get-port-please' -import { describe, expect, it } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' import { runCommand } from '../../src' const fixtureDir = fileURLToPath(new URL('../fixtures/dev', import.meta.url)) +const certsDir = fileURLToPath(new URL('../../../../playground/certs', import.meta.url)) +const httpsCert = join(certsDir, 'cert.dummy') +const httpsKey = join(certsDir, 'key.dummy') +const httpsPfx = join(certsDir, 'pfx.dummy') + describe('dev server', () => { + afterEach(() => { + vi.unstubAllEnvs() + }) + it('should expose dev server address to nuxt options', { timeout: 50_000 }, async () => { await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) const host = '127.0.0.1' @@ -93,65 +102,231 @@ describe('dev server', () => { } }) - it('should expose dev server https options to nuxt options', { timeout: 50_000 }, async () => { - await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) - const host = '127.0.0.1' - const port = await getPort({ host, port: 3034 }) - const { result: { close } } = await runCommand('dev', [ - `--https`, - `--https.passphrase=pass`, - `--host=${host}`, - `--port=${port}`, - `--cwd=${fixtureDir}`, - ], { - overrides: { - modules: [ - fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), - ], - }, - }) as any - await close() - const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) - expect(options).toMatchObject({ - https: { passphrase: 'pass' }, - host, - port, - url: `https://${host}:${port}/`, + describe('https options', async () => { + const httpsCertValue = (await readFile(httpsCert, { encoding: 'ascii' })).split(/\r?\n/) + const httpsKeyValue = (await readFile(httpsKey, { encoding: 'ascii' })).split(/\r?\n/) + + it('should be applied cert and key from commandline', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3601 }) + const { result: { close } } = await runCommand('dev', [ + `--https`, + `--https.cert=${httpsCert}`, + `--https.key=${httpsKey}`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const { https, ...options } = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `https://${host}:${port}/`, + }) + expect(https).toBeTruthy() + expect(https.cert.split(/\r?\n/)).toEqual(httpsCertValue) + expect(https.key.split(/\r?\n/)).toEqual(httpsKeyValue) }) - }) - it('should override devServer options with commandline options', { timeout: 50_000 }, async () => { - await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) - const host = '127.0.0.1' - const port = await getPort({ host, port: 3050 }) - const { result: { close } } = await runCommand('dev', [ - `--https=false`, - `--host=${host}`, - `--port=${port}`, - `--cwd=${fixtureDir}`, - ], { - overrides: { - devServer: { - host: 'localhost', - port: 3000, - https: { - cert: 'invalid', - key: 'invalid', - passphrase: 'dev', + it('should be applied pfx and passphrase from commandline', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3602 }) + const { result: { close } } = await runCommand('dev', [ + `--https`, + `--https.pfx=${httpsPfx}`, + `--https.passphrase=pass`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const { https, ...options } = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `https://${host}:${port}/`, + }) + expect(https).toBeTruthy() + expect(https.cert.split(/\r?\n/)).toEqual(httpsCertValue) + expect(https.key.split(/\r?\n/)).toEqual(httpsKeyValue) + }) + + it('should be override from commandline', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3603 }) + const { result: { close } } = await runCommand('dev', [ + `--https.cert=${httpsCert}`, + `--https.key=${httpsKey}`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + devServer: { + https: { + cert: 'invalid-cert.pem', + key: 'invalid-key.pem', + host: 'localhost', + port: 3000, + }, }, + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], }, - modules: [ - fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), - ], - }, - }) as any - await close() - const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) - expect(options).toMatchObject({ - https: false, - host, - port, - url: `http://${host}:${port}/`, + }) as any + await close() + const { https, ...options } = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `https://${host}:${port}/`, + }) + expect(https).toBeTruthy() + expect(https.cert.split(/\r?\n/)).toEqual(httpsCertValue) + expect(https.key.split(/\r?\n/)).toEqual(httpsKeyValue) + }) + + it('should be disabled from commandline', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3604 }) + const { result: { close } } = await runCommand('dev', [ + `--https=false`, + `--host=${host}`, + `--port=${port}`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + devServer: { + https: true, + host: 'localhost', + port: 3000, + }, + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + https: false, + host, + port, + url: `http://${host}:${port}/`, + }) + }) + }) + + describe('applied environment variables', async () => { + const httpsCertValue = (await readFile(httpsCert, { encoding: 'ascii' })).split(/\r?\n/) + const httpsKeyValue = (await readFile(httpsKey, { encoding: 'ascii' })).split(/\r?\n/) + + it('should be applied from NUXT_ environment variables', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3701 }) + + vi.stubEnv('NUXT_HOST', host) + vi.stubEnv('NUXT_PORT', `${port}`) + vi.stubEnv('NUXT_SSL_CERT', httpsCert) + vi.stubEnv('NUXT_SSL_KEY', httpsKey) + + const { result: { close } } = await runCommand('dev', [ + `--https`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const { https, ...options } = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `https://${host}:${port}/`, + }) + expect(https).toBeTruthy() + expect(https.cert.split(/\r?\n/)).toEqual(httpsCertValue) + expect(https.key.split(/\r?\n/)).toEqual(httpsKeyValue) + }) + + it('should be applied from NITRO_ environment variables', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3702 }) + + vi.stubEnv('NITRO_HOST', host) + vi.stubEnv('NITRO_PORT', `${port}`) + vi.stubEnv('NITRO_SSL_CERT', httpsCert) + vi.stubEnv('NITRO_SSL_KEY', httpsKey) + + const { result: { close } } = await runCommand('dev', [ + `--https`, + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const { https, ...options } = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `https://${host}:${port}/`, + }) + expect(https).toBeTruthy() + expect(https.cert.split(/\r?\n/)).toEqual(httpsCertValue) + expect(https.key.split(/\r?\n/)).toEqual(httpsKeyValue) + }) + + it('should be applied from HOST and PORT environment variables', { timeout: 50_000 }, async () => { + await rm(join(fixtureDir, '.nuxt'), { recursive: true, force: true }) + const host = '127.0.0.1' + const port = await getPort({ host, port: 3703 }) + + vi.stubEnv('HOST', host) + vi.stubEnv('PORT', `${port}`) + + const { result: { close } } = await runCommand('dev', [ + `--cwd=${fixtureDir}`, + ], { + overrides: { + modules: [ + fileURLToPath(new URL('../fixtures/log-dev-server-options.ts', import.meta.url)), + ], + }, + }) as any + await close() + const options = await readFile(join(fixtureDir, '.nuxt/dev-server.json'), 'utf-8').then(JSON.parse) + expect(options).toMatchObject({ + host, + port, + url: `http://${host}:${port}/`, + }) }) }) }) diff --git a/playground/certs/cert.dummy b/playground/certs/cert.dummy new file mode 100644 index 000000000..4c6e93016 --- /dev/null +++ b/playground/certs/cert.dummy @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2DCCAsCgAwIBAgIFNTkzOTIwDQYJKoZIhvcNAQELBQAwdTESMBAGA1UEAxMJ +bG9jYWxob3N0MQswCQYDVQQGEwJVUzERMA8GA1UECBMITWljaGlnYW4xEDAOBgNV +BAcTB0JlcmtsZXkxFTATBgNVBAoTDFRlc3RpbmcgQ29ycDEWMBQGA1UECxMNSVQg +ZGVwYXJ0bWVudDAgFw0yNTExMTYwMzIxNDZaGA8yMTI1MTAyMzAzMjE0NlowdTES +MBAGA1UEAxMJbG9jYWxob3N0MQswCQYDVQQGEwJVUzERMA8GA1UECBMITWljaGln +YW4xEDAOBgNVBAcTB0JlcmtsZXkxFTATBgNVBAoTDFRlc3RpbmcgQ29ycDEWMBQG +A1UECxMNSVQgZGVwYXJ0bWVudDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALltbdcjx7u+018fT9dOIUZbZUt9CoHpZWbWSohMz9YfVabB99SL+7AiM30/ +l4XCXO5DhhUSxqwUlQ2pAcjnEHxmVpvuiXwTHwItfiF+UDxyCIlfMrj8Tc8ltB41 +KPpfgUuc1mb6fsISjEgRkLNW0VIXJDNenlvpx/uXf4MG+6My1T7TFVAhEulZu3wv +b62Q5WR6Ud1G7pMDrEcRcPHkuh3CeQ3OW11hCrwMh2IaLPCd+PDTkwfOaKXYY3/m +raLSmCb/PXVsN7qcUBd1+bwlMblteeJykoZfYawVgn8/+sVdtxBsQftAYOComz4a +a4iJsuKEPoLf8VwaVUaIMpZEVtcCAwEAAaNtMGswDAYDVR0TAQH/BAIwADAOBgNV +HQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMCwGA1Ud +EQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG +9w0BAQsFAAOCAQEAZsnDKhb3OjHgBg1rz2yZg0FFD7oPeyNJNpxXScIOmCIsh0f0 +oYx8b17/4dbEg812EHpdUJeceWfvKg+vfylmL6hZ04eY4ymUjf5jrW9iuDd3mmLY ++wsARPjvBvw/iA4BPYg16HUSqUEWfwG/IeQqpu+C75RCie604nt36nDoTQv8QLCx +Et6wvXH9Ucz4CA5MjFJbqaM62CJU+Qeu4sVFgY/KW3UQEJboD51hutxKBMWkkZGL +l5svZhTqUoPSFgdQTHeu4Tku5ZSdwgEBBE2cf8HPOET99aFhPvCVg52IysfW2UNs +BKo0D/Vy+dCX8CGldS59CLx5zKta30fSLlq6vw== +-----END CERTIFICATE----- diff --git a/playground/certs/key.dummy b/playground/certs/key.dummy new file mode 100644 index 000000000..ab1ed8138 --- /dev/null +++ b/playground/certs/key.dummy @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuW1t1yPHu77TXx9P104hRltlS30KgellZtZKiEzP1h9VpsH3 +1Iv7sCIzfT+XhcJc7kOGFRLGrBSVDakByOcQfGZWm+6JfBMfAi1+IX5QPHIIiV8y +uPxNzyW0HjUo+l+BS5zWZvp+whKMSBGQs1bRUhckM16eW+nH+5d/gwb7ozLVPtMV +UCES6Vm7fC9vrZDlZHpR3UbukwOsRxFw8eS6HcJ5Dc5bXWEKvAyHYhos8J348NOT +B85opdhjf+atotKYJv89dWw3upxQF3X5vCUxuW154nKShl9hrBWCfz/6xV23EGxB ++0Bg4KibPhpriImy4oQ+gt/xXBpVRogylkRW1wIDAQABAoIBAB9dZLZ+7WKTATL2 +W216YEOD4yr1MClQXuAZwEq033UDINxPtAmGUiD1cAswDgPIoCqHTm9TGTrzUlEY +tN4UQ6QfNWgz3ZqYq2aVZl/o+052JX6DFVPYDZtL798qM8/CBt9Q3K1XkshmFcd8 +/SJwvYBqvKtZxmSas0KZ2i5CKJ9uiHEWi/ZCs6pUJdRojxfkfeHLMujA7EDBtZic +AKqH698jo7+m8hddczM7XOlBVe6MAn2fWs4VlgVK4gfw4V0xtZt5wvNhyh69YTUZ +wtMTaEULPwPtmxSTdWvb0Z4kUftXLRS3jd/ibe7utQLoR579ZPoNL0Ixma8sIA9j +TV+b9akCgYEA5wJufOg7uFXD1bL/TsnU9ciNs7rof6SK+Hc7IrLFVk7a5SeAjcAJ +oar70pnaGE0TgLLMXysi/2XZL0y45a3ihgl3CoLGJBGOv4QcgccmFrZqYUGCV0Cv +oz7PskhcmcneyWnoI/twbbyYxkQNww+5LSTN8iAaVcb+3G1YZPaQ2AkCgYEAzXym +btesxvDXHurQdviBKEjp7yhvSo44Q54ahEW8n1RiGfjgwFcjF1jZXxqGml/bIiu4 +AYsSeUqBrFXAXq9AlA6DThFJtC5EK385do1WUhcaU53FlvWt2atmRXuUl+rgaBrS +b43uvLyFJZezQILzvYkaHJjh8ioKOkPzLW2ar98CgYB0oJ6lgx27d9lSB3esEGvq +1qDrz35YCvt6a7+4SeclJtSOgr39UqnKLCfM8I3SXP9up1ZU6dNWe9YFckea9Yn6 +v8aQ0Os2BIM8H3fA8YlCSEA277rdUDQcR7bWPIA7yFYo+8YOfIALdv7ugicshsCn +kQBEsH57Necv5CiPeIgx+QKBgG+BY6MkX/p4eJOrYkIc6aFdp6wCqhmwATIYGlWK +ridbl/x2BCf7YOxrZ1FnSIF+4J+zT59uwzCUULeetMvsl8N/+JqlYPRoYs+jsx/0 +5FGZfczAAZfAa32Bt/aeb+zcJLf5ThYA0/sQ5cOXhUrNhMxmGIhKIdnSHEiv1Mbj +AhzLAoGBAKDYjXW1FNIR3le4Ngs8adCPN18iJ7rXSYPnen5e40g3i1vQGKwxMQpg +aEKByb1RWSH4fZDToiC+CPqrimgu24mgYJNMD9W7M8iizR2/5vKNJ/ABqL1/SV0L +OsvTc2QFx005TGOLteaw1mhqjSGptNHzHhPQ01v2gcIkobBk0N5y +-----END RSA PRIVATE KEY----- diff --git a/playground/certs/pfx.dummy b/playground/certs/pfx.dummy new file mode 100644 index 0000000000000000000000000000000000000000..d993b5cde7f07582df18d5d74a780724b319dc23 GIT binary patch literal 2589 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