Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions config/local-nginx.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Local development nginx config for quarto-hub
# Mirrors production setup but without TLS
# NOTE: This file is loaded dynamically by scripts/local-prod-nginx.sh with DIST_PATH substitution
# NOTE: This file is loaded dynamically by scripts/local-prod-nginx.sh with
# DIST_PATH and NGINX_PORT substitution

# Map WebSocket upgrade header to connection type
map $http_upgrade $connection_upgrade {
Expand All @@ -26,7 +27,7 @@ server {
}

server {
listen 8080;
listen NGINX_PORT;
server_name localhost;

# Security headers (same as production)
Expand Down
2 changes: 1 addition & 1 deletion hub-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ npm run local-prod -- --port 9000

Two modes available:
- `local-prod` - Node.js proxy (fast, recommended)
- `local-prod:nginx` - nginx in Docker (tests actual nginx config)
- `local-prod:nginx` - local nginx (tests actual nginx config); takes the same `--port` option

See [`../scripts/README.md`](../scripts/README.md) for details and troubleshooting.

Expand Down
4 changes: 4 additions & 0 deletions hub-client/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ WASM rebuild is needed for a changelog-only edit.

-->

### 2026-08-27

- [`e279bc9c9`](https://github.com/quarto-dev/q2/commits/e279bc9c9): `local-prod:nginx` now accepts `--port` like plain `local-prod`, and starts correctly when `OIDC_CLIENT_ID` in your shell enables hub auth — the readiness check previously failed on the hub's 401 even though the hub was up.

### 2026-08-26

- [`ca959a67`](https://github.com/quarto-dev/q2/commits/ca959a67): Comment bubbles now render comments richly — emphasis, inline code, quotes, links (external links open in a new tab, document links navigate the preview), and images (clamped to the bubble). Content that has no sensible bubble form, like nested editorial marks, shows an explicit "unsupported content" chip instead of rendering incorrectly.
Expand Down
11 changes: 10 additions & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This directory contains automation scripts for the Kyoto project.
Two modes available:

**`local-prod.sh`** (Node.js proxy) - Quick setup, no dependencies
**`local-prod-nginx.sh`** (nginx in Docker) - Test actual nginx config
**`local-prod-nginx.sh`** (native nginx) - Test actual nginx config

### What it does

Expand Down Expand Up @@ -78,13 +78,22 @@ Fast setup, tests WebSocket proxying and routing. Good for 90% of development.
```bash
cd hub-client
npm run local-prod:nginx

# Custom port works here too (pass it to both build and run):
npm run build:local-prod -- --port 9000
npm run local-prod:nginx -- --port 9000
```

Tests the actual nginx configuration from production. Use when:
- Testing nginx config changes before deploying
- Validating gzip compression, security headers
- Debugging nginx-specific issues

If `OIDC_CLIENT_ID` is set in your shell, the local hub picks it up and runs
with auth enabled (the script logs "Auth enabled via OIDC_CLIENT_ID from the
environment"). The readiness check treats the resulting 401 as "up". Unset
the variable if you want the default no-auth local setup.

**Architecture differences:**
- **Node.js mode:** Browser → Node.js proxy (port 8080) → hub (port 3000)
- **Nginx mode:** Browser → nginx (Docker, port 8080) → hub (host, port 3000)
Expand Down
13 changes: 9 additions & 4 deletions scripts/local-prod-nginx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
HUB_CLIENT_DIR="$PROJECT_ROOT/hub-client"
DATA_DIR="$PROJECT_ROOT/.local-prod-data"
HUB_PORT=3000
NGINX_PORT=8080
NGINX_PORT="$(node "$SCRIPT_DIR/local-prod-port.mjs" "$@")"
Q2_SANDBOXED_PREVIEW_PORT=8081

# Color output
Expand Down Expand Up @@ -115,7 +115,7 @@ mkdir -p "$DATA_DIR"
# Generate nginx config with absolute paths
log_step "Generating nginx configuration..."
DIST_PATH_ABSOLUTE="$HUB_CLIENT_DIR/dist"
sed "s|DIST_PATH|$DIST_PATH_ABSOLUTE|g" "$PROJECT_ROOT/config/local-nginx.conf" > "$DATA_DIR/nginx.conf"
sed "s|DIST_PATH|$DIST_PATH_ABSOLUTE|g; s|NGINX_PORT|$NGINX_PORT|g" "$PROJECT_ROOT/config/local-nginx.conf" > "$DATA_DIR/nginx.conf"

# Add required nginx directives (pid, error_log, events, http wrapper)
cat > "$DATA_DIR/nginx.conf.tmp" << EOF
Expand Down Expand Up @@ -176,10 +176,15 @@ if ! kill -0 "$HUB_PID" 2>/dev/null; then
exit 1
fi

# Wait for hub health endpoint
# Wait for hub health endpoint. Readiness = any HTTP response, not a 2xx:
# with auth enabled (OIDC_CLIENT_ID set in the environment), /health
# requires credentials and returns 401 even though the hub is up.
for i in {1..10}; do
if curl -f http://127.0.0.1:$HUB_PORT/health >/dev/null 2>&1; then
if curl -s -o /dev/null http://127.0.0.1:$HUB_PORT/health 2>/dev/null; then
log_info "Hub is ready (PID: $HUB_PID)"
if [ -n "${OIDC_CLIENT_ID:-}" ]; then
log_info "Auth enabled via OIDC_CLIENT_ID from the environment"
fi
break
fi
if [ $i -eq 10 ]; then
Expand Down
25 changes: 25 additions & 0 deletions scripts/local-prod-port.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ describe('parseLocalProdPort', () => {
expect(() => parseLocalProdPort(['--port', '70000'])).toThrow();
});

it('nginx launcher honors --port like the plain launcher', async () => {
const nginxLauncher = await readFile(new URL('./local-prod-nginx.sh', import.meta.url), 'utf8');
const nginxConfig = await readFile(new URL('../config/local-nginx.conf', import.meta.url), 'utf8');

// The nginx launcher parses --port via the shared parser, same as local-prod.sh
expect(nginxLauncher).toContain('NGINX_PORT="$(node "$SCRIPT_DIR/local-prod-port.mjs" "$@")"');
expect(nginxLauncher).not.toMatch(/^NGINX_PORT=8080$/m);
// ...and substitutes it into the generated nginx config
expect(nginxLauncher).toContain('s|NGINX_PORT|$NGINX_PORT|g');
// The template carries the placeholder, not a hardcoded main port
expect(nginxConfig).toContain('listen NGINX_PORT;');
expect(nginxConfig).not.toContain('listen 8080;');
});

it('nginx launcher hub readiness check accepts 401 (auth-enabled setups)', async () => {
// With OIDC_CLIENT_ID in the environment the hub enables auth, and
// /health requires credentials: `curl -f` fails on the 401 even though
// the hub is up. Readiness must mean "any HTTP response", not "2xx".
const nginxLauncher = await readFile(new URL('./local-prod-nginx.sh', import.meta.url), 'utf8');
const hubHealth = nginxLauncher.match(/curl [^\n]*\/health[^\n]*/);
expect(hubHealth).not.toBeNull();
const healthCurl = hubHealth[0];
expect(healthCurl).not.toContain(' -f');
});

it('keeps the hub proxy on port 3000', async () => {
const serverScript = await readFile(new URL('./local-prod-server.mjs', import.meta.url), 'utf8');
const launcherScript = await readFile(new URL('./local-prod.sh', import.meta.url), 'utf8');
Expand Down
Loading