Skip to content

Commit fbe0446

Browse files
slorberclaude
andauthored
fix(core): docusaurus start prints and opens an https URL when HTTPS is enabled (AI-assisted) (#12716)
The open URL protocol was only derived from the unprefixed `HTTPS=true` env var, ignoring `--https`, `--ssl-cert`/`--ssl-key` and the `DOCUSAURUS_*` env vars. The HTTPS config is now resolved once, and shared between the open URL and the dev server. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4626d40 commit fbe0446

4 files changed

Lines changed: 104 additions & 14 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {afterEach, describe, expect, it, vi} from 'vitest';
9+
import path from 'node:path';
10+
import {createOpenUrlContext} from '../utils';
11+
import type {StartCLIOptions} from '../start';
12+
13+
vi.mock('../../../server/getHostPort', () => ({
14+
getHostPort: async () => ({host: 'localhost', port: 3000}),
15+
}));
16+
17+
describe('createOpenUrlContext', () => {
18+
function getCertFixture(name: string) {
19+
return path.join(
20+
__dirname,
21+
'../../../webpack/utils/__tests__/__fixtures__/getHttpsConfig',
22+
name,
23+
);
24+
}
25+
26+
async function getOpenUrl(cliOptions: StartCLIOptions) {
27+
const context = await createOpenUrlContext({cliOptions});
28+
return context.getOpenUrl({baseUrl: '/baseUrl/', router: 'browser'});
29+
}
30+
31+
afterEach(() => {
32+
vi.unstubAllEnvs();
33+
});
34+
35+
it('returns an http url by default', async () => {
36+
await expect(getOpenUrl({})).resolves.toBe(
37+
'http://localhost:3000/baseUrl/',
38+
);
39+
});
40+
41+
it('returns an https url with --https', async () => {
42+
await expect(getOpenUrl({https: true})).resolves.toBe(
43+
'https://localhost:3000/baseUrl/',
44+
);
45+
});
46+
47+
it('returns an https url with --ssl-cert and --ssl-key', async () => {
48+
await expect(
49+
getOpenUrl({
50+
sslCert: getCertFixture('host.crt'),
51+
sslKey: getCertFixture('host.key'),
52+
}),
53+
).resolves.toBe('https://localhost:3000/baseUrl/');
54+
});
55+
56+
it('returns an https url with DOCUSAURUS_HTTPS=true', async () => {
57+
vi.stubEnv('DOCUSAURUS_HTTPS', 'true');
58+
await expect(getOpenUrl({})).resolves.toBe(
59+
'https://localhost:3000/baseUrl/',
60+
);
61+
});
62+
63+
it('returns an http url with DOCUSAURUS_HTTPS=false', async () => {
64+
vi.stubEnv('DOCUSAURUS_HTTPS', 'false');
65+
await expect(getOpenUrl({})).resolves.toBe(
66+
'http://localhost:3000/baseUrl/',
67+
);
68+
});
69+
70+
it('shares the resolved HTTPS config with the dev server', async () => {
71+
const context = await createOpenUrlContext({
72+
cliOptions: {
73+
sslCert: getCertFixture('host.crt'),
74+
sslKey: getCertFixture('host.key'),
75+
},
76+
});
77+
expect(context.httpsConfig).toEqual({
78+
cert: expect.any(Buffer),
79+
key: expect.any(Buffer),
80+
});
81+
});
82+
});

‎packages/docusaurus/src/commands/start/utils.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {normalizeUrl} from '@docusaurus/utils';
1111
import logger, {PerfLogger} from '@docusaurus/logger';
1212
import {realpath} from '@docusaurus/fs';
1313
import {getHostPort} from '../../server/getHostPort';
14+
import getHttpsConfig, {
15+
type HttpsConfig,
16+
} from '../../webpack/utils/getHttpsConfig';
1417
import {
1518
loadSite,
1619
type LoadSiteParams,
@@ -44,6 +47,7 @@ function getOpenUrlOrigin(
4447
export type OpenUrlContext = {
4548
host: string;
4649
port: number;
50+
httpsConfig: HttpsConfig;
4751
getOpenUrl: ({
4852
baseUrl,
4953
router,
@@ -53,12 +57,17 @@ export type OpenUrlContext = {
5357
}) => string;
5458
};
5559

56-
async function createOpenUrlContext({
60+
export async function createOpenUrlContext({
5761
cliOptions,
5862
}: {
5963
cliOptions: StartCLIOptions;
6064
}): Promise<OpenUrlContext> {
61-
const protocol: string = process.env.HTTPS === 'true' ? 'https' : 'http';
65+
const httpsConfig = await getHttpsConfig({
66+
https: cliOptions.https,
67+
sslCert: cliOptions.sslCert,
68+
sslKey: cliOptions.sslKey,
69+
});
70+
const protocol = httpsConfig ? 'https' : 'http';
6271

6372
const {host, port} = await getHostPort(cliOptions);
6473
if (port === null) {
@@ -73,7 +82,7 @@ async function createOpenUrlContext({
7382
]);
7483
};
7584

76-
return {host, port, getOpenUrl};
85+
return {host, port, httpsConfig, getOpenUrl};
7786
}
7887

7988
type StartParams = {

‎packages/docusaurus/src/commands/start/webpack.ts‎

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
} from '@docusaurus/bundler';
1515
import logger from '@docusaurus/logger';
1616
import {createPollingOptions} from './watcher';
17-
import getHttpsConfig from '../../webpack/utils/getHttpsConfig';
1817
import {
1918
createConfigureWebpackUtils,
2019
executePluginsConfigureWebpack,
@@ -32,6 +31,7 @@ import type {Configuration as DevServerConfig} from 'webpack-dev-server';
3231
import type {Compiler as RspackCompiler} from '@rspack/core';
3332
import type {Configuration as RspackDevServerConfig} from '@rspack/dev-server';
3433
import type {OpenUrlContext} from './utils';
34+
import type {HttpsConfig} from '../../webpack/utils/getHttpsConfig';
3535

3636
// E2E_TEST=true docusaurus start
3737
// Makes "docusaurus start" exit immediately on success/error, for E2E test
@@ -54,27 +54,23 @@ function registerWebpackE2ETestHook(compiler: Compiler) {
5454
});
5555
}
5656

57-
async function createDevServerConfig({
57+
function createDevServerConfig({
5858
cliOptions,
5959
props,
6060
host,
6161
port,
62+
httpsConfig,
6263
}: {
6364
cliOptions: StartCLIOptions;
6465
props: Props;
6566
host: string;
6667
port: number;
67-
}): Promise<DevServerConfig> {
68+
httpsConfig: HttpsConfig;
69+
}): DevServerConfig {
6870
const {baseUrl, siteDir, siteConfig} = props;
6971

7072
const pollingOptions = createPollingOptions(cliOptions);
7173

72-
const httpsConfig = await getHttpsConfig({
73-
https: cliOptions.https,
74-
sslCert: cliOptions.sslCert,
75-
sslKey: cliOptions.sslKey,
76-
});
77-
7874
// https://webpack.js.org/configuration/dev-server
7975
return {
8076
hot: cliOptions.hotOnly ? 'only' : true,
@@ -178,11 +174,12 @@ export async function createWebpackDevServer({
178174
const compiler = props.currentBundler.instance(config);
179175
registerWebpackE2ETestHook(compiler);
180176

181-
const defaultDevServerConfig: DevServerConfig = await createDevServerConfig({
177+
const defaultDevServerConfig: DevServerConfig = createDevServerConfig({
182178
cliOptions,
183179
props,
184180
host: openUrlContext.host,
185181
port: openUrlContext.port,
182+
httpsConfig: openUrlContext.httpsConfig,
186183
});
187184

188185
// Allow plugin authors to customize/override devServer config

‎packages/docusaurus/src/webpack/utils/getHttpsConfig.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,14 @@ You only provided a ${cert ? 'certificate' : 'key'} (with ${fileProvided.source}
156156
}
157157
}
158158

159+
export type HttpsConfig = boolean | {cert: Buffer; key: Buffer};
160+
159161
// Get the https config
160162
// Return cert files if provided via CLI or env, otherwise just true or false.
161163
// CLI options take precedence over env vars.
162164
export default async function getHttpsConfig(
163165
options: Partial<HttpsConfigOptions> = {},
164-
): Promise<boolean | {cert: Buffer; key: Buffer}> {
166+
): Promise<HttpsConfig> {
165167
const cwd = await realpath(process.cwd());
166168

167169
const [cert, key] = await Promise.all([

0 commit comments

Comments
 (0)