| #!/usr/bin/env bash | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| ############################################################## | |
| # Local development script for doris-website | |
| # | |
| # Usage: | |
| # ./local_dev.sh [command] [options] | |
| # | |
| # Commands: | |
| # start Start dev server (English, default) | |
| # start-landing Start dev server with ONLY landing pages | |
| # (home-next, use-cases-next, etc.). Docs/blog/ | |
| # community/releases/search are all disabled. | |
| # Drops peak memory from ~20GB to ~1-2GB. | |
| # start-zh Start dev server (Chinese) | |
| # build Full production build (en + zh-CN) | |
| # build-en Production build (English only) | |
| # build-docs-next Start docs-next dev server (hot reload, current only) | |
| # build-docs-next-all Build docs-next only (en + zh-CN) | |
| # serve Serve a previous production build | |
| # install Install dependencies only | |
| # clean Clean build artifacts and caches | |
| # help Show this help message | |
| # | |
| # Options: | |
| # --port PORT Dev server port (default: 3000) | |
| # --host HOST Dev server host (default: localhost) | |
| # --skip-install Skip yarn install step | |
| # --versions LIST Comma-separated versions to build | |
| # e.g. --versions "4.x" (faster builds) | |
| # --max-mem MB Node.js max old space size in MB | |
| # (default: 8192) | |
| # | |
| # Examples: | |
| # ./local_dev.sh # start English dev server | |
| # ./local_dev.sh start --port 8080 # start on port 8080 | |
| # ./local_dev.sh start-zh # start Chinese dev server | |
| # ./local_dev.sh build # full build (slow) | |
| # ./local_dev.sh build --versions "4.x" # build only 4.x version | |
| # ./local_dev.sh build-docs-next # start docs-next dev server (hot reload) | |
| # ./local_dev.sh build-docs-next-all # build docs-next only (en + zh-CN) | |
| # ./local_dev.sh clean # clean caches | |
| ############################################################## | |
| set -euo pipefail | |
| # ─── Resolve project root (directory of this script) ───────── | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| PROJECT_ROOT="${SCRIPT_DIR}" | |
| # ─── Colors ────────────────────────────────────────────────── | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| CYAN='\033[0;36m' | |
| BOLD='\033[1m' | |
| NC='\033[0m' # No Color | |
| info() { echo -e "${BLUE}[INFO]${NC} $*"; } | |
| ok() { echo -e "${GREEN}[OK]${NC} $*"; } | |
| warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } | |
| error() { echo -e "${RED}[ERROR]${NC} $*"; } | |
| step() { echo -e "\n${CYAN}${BOLD}==> $*${NC}"; } | |
| # ─── Discover Node.js and Yarn (no global env modification) ── | |
| setup_node_env() { | |
| # Try common Node.js install locations (macOS + Linux) | |
| local search_paths=( | |
| "/usr/local/bin" | |
| "/usr/bin" | |
| "/opt/homebrew/bin" | |
| "${HOME}/.nvm/versions/node/$(ls -1 ${HOME}/.nvm/versions/node/ 2>/dev/null | sort -V | tail -1)/bin" | |
| "${HOME}/.volta/bin" | |
| "${HOME}/.fnm/aliases/default/bin" | |
| "${HOME}/.local/bin" | |
| "/snap/node/current/bin" | |
| ) | |
| # If node is already on PATH, no need to modify | |
| if command -v node &>/dev/null && command -v yarn &>/dev/null; then | |
| NODE_BIN="$(command -v node)" | |
| YARN_BIN="$(command -v yarn)" | |
| return 0 | |
| fi | |
| # Search for node in common locations | |
| local found_path="" | |
| for p in "${search_paths[@]}"; do | |
| if [[ -x "${p}/node" ]]; then | |
| found_path="${p}" | |
| break | |
| fi | |
| done | |
| if [[ -z "${found_path}" ]]; then | |
| error "Node.js not found! Please install Node.js >= 18." | |
| error "Searched: ${search_paths[*]}" | |
| echo "" | |
| echo "Install options:" | |
| echo " macOS: brew install node" | |
| echo " Linux: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - && sudo apt-get install -y nodejs" | |
| echo " or visit https://nodejs.org/" | |
| exit 1 | |
| fi | |
| # Prepend to PATH only within this script's process | |
| export PATH="${found_path}:${PATH}" | |
| NODE_BIN="${found_path}/node" | |
| # Ensure yarn is available; install locally if needed | |
| if [[ -x "${found_path}/yarn" ]]; then | |
| YARN_BIN="${found_path}/yarn" | |
| elif command -v yarn &>/dev/null; then | |
| YARN_BIN="$(command -v yarn)" | |
| else | |
| warn "Yarn not found, installing via npm (local to this script)..." | |
| "${found_path}/npm" install -g yarn 2>/dev/null || { | |
| error "Failed to install yarn. Please install manually: npm install -g yarn" | |
| exit 1 | |
| } | |
| YARN_BIN="${found_path}/yarn" | |
| fi | |
| } | |
| # ─── Validate environment ─────────────────────────────────── | |
| validate_env() { | |
| step "Checking environment" | |
| setup_node_env | |
| local node_version | |
| node_version="$("${NODE_BIN}" --version)" | |
| local node_major="${node_version#v}" | |
| node_major="${node_major%%.*}" | |
| if (( node_major < 18 )); then | |
| error "Node.js >= 18 is required, found ${node_version}" | |
| exit 1 | |
| fi | |
| local yarn_version | |
| yarn_version="$("${YARN_BIN}" --version)" | |
| ok "Node.js ${node_version} (${NODE_BIN})" | |
| ok "Yarn v${yarn_version} (${YARN_BIN})" | |
| ok "Project ${PROJECT_ROOT}" | |
| } | |
| # ─── Install dependencies ─────────────────────────────────── | |
| do_install() { | |
| step "Installing dependencies" | |
| cd "${PROJECT_ROOT}" | |
| if [[ -d "node_modules" ]] && [[ -f "node_modules/.yarn-integrity" ]]; then | |
| info "node_modules exists, running yarn to sync..." | |
| else | |
| info "Installing from scratch (this may take a few minutes)..." | |
| fi | |
| "${YARN_BIN}" install --frozen-lockfile 2>/dev/null || "${YARN_BIN}" install | |
| ok "Dependencies installed" | |
| } | |
| # ─── Set DOCS_VERSIONS env var for selective version builds ── | |
| # This uses the onlyIncludeVersions option in docusaurus.config.js | |
| # instead of modifying versions.json (which is fragile). | |
| apply_versions_env() { | |
| local filter="$1" | |
| if [[ -n "${filter}" ]]; then | |
| export DOCS_VERSIONS="${filter}" | |
| info "DOCS_VERSIONS=${filter} (only these versions will be built)" | |
| else | |
| info "DOCS_VERSIONS not set (all versions will be built)" | |
| fi | |
| } | |
| # ─── Commands ──────────────────────────────────────────────── | |
| cmd_start() { | |
| local port="${OPT_PORT}" | |
| local host="${OPT_HOST}" | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| apply_versions_env "${OPT_VERSIONS}" | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| step "Starting dev server (English) on ${host}:${port}" | |
| info "Press Ctrl+C to stop" | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus start --no-open --host "${host}" --port "${port}" | |
| } | |
| cmd_start_landing() { | |
| local port="${OPT_PORT}" | |
| local host="${OPT_HOST}" | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| export LANDING_ONLY=true | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| step "Starting LANDING-ONLY dev server on ${host}:${port}" | |
| info "Disabled: docs, blog, community, releases, docs-next, search." | |
| info "Only landing pages (home-next, use-cases-next, etc.) render." | |
| info "Cross-links to /docs/* etc. will 404 — that's expected." | |
| info "Press Ctrl+C to stop" | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus start --no-open --host "${host}" --port "${port}" | |
| } | |
| cmd_start_zh() { | |
| local port="${OPT_PORT}" | |
| local host="${OPT_HOST}" | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| apply_versions_env "${OPT_VERSIONS}" | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| step "Starting dev server (Chinese) on ${host}:${port}" | |
| info "Press Ctrl+C to stop" | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus start --no-open --locale zh-CN --host "${host}" --port "${port}" | |
| } | |
| cmd_build() { | |
| local locales="${1:-en}" | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| apply_versions_env "${OPT_VERSIONS}" | |
| step "Building site (locales: ${locales})" | |
| info "NODE_OPTIONS=--max-old-space-size=${OPT_MAX_MEM}" | |
| info "This may take 10-30 minutes for full builds..." | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| # Build locale arguments | |
| local locale_args="" | |
| for locale in ${locales}; do | |
| locale_args+=" --locale ${locale}" | |
| done | |
| "${YARN_BIN}" docusaurus build ${locale_args} | |
| ok "Build completed! Output in: ${PROJECT_ROOT}/build/" | |
| info "Run './local_dev.sh serve' to preview the build." | |
| } | |
| cmd_build_docs_next_only() { | |
| local port="${OPT_PORT}" | |
| local host="${OPT_HOST}" | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| # Disable the legacy docs plugin: DOCS_VERSIONS=current is meant for | |
| # docs-next, but the legacy docs plugin has no `current` version and | |
| # would fail validation on lastVersion. | |
| export SKIP_DOCS=true | |
| apply_versions_env "current" | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| step "Starting docs-next dev server on ${host}:${port}" | |
| info "Navigate to http://${host}:${port}/docs-next/dev/" | |
| info "Press Ctrl+C to stop" | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus start --no-open --locale zh-CN --host "${host}" --port "${port}" | |
| } | |
| cmd_build_docs_next_all() { | |
| validate_env | |
| if [[ "${OPT_SKIP_INSTALL}" != "true" ]]; then | |
| do_install | |
| fi | |
| # Same rationale as cmd_build_docs_next_only: avoid feeding "current" | |
| # into the legacy docs plugin's lastVersion validation. | |
| export SKIP_DOCS=true | |
| apply_versions_env "current" | |
| export NODE_OPTIONS="--max-old-space-size=${OPT_MAX_MEM}" | |
| step "Building docs-next only (locales: en zh-CN)" | |
| info "NODE_OPTIONS=--max-old-space-size=${OPT_MAX_MEM}" | |
| info "This may take a few minutes..." | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus build --locale en --locale zh-CN | |
| ok "Build completed! Output in: ${PROJECT_ROOT}/build/" | |
| info "Run './local_dev.sh serve' to preview the build." | |
| } | |
| cmd_serve() { | |
| validate_env | |
| if [[ ! -d "${PROJECT_ROOT}/build" ]]; then | |
| error "No build directory found. Run './local_dev.sh build' first." | |
| exit 1 | |
| fi | |
| local port="${OPT_PORT}" | |
| local host="${OPT_HOST}" | |
| step "Serving build on ${host}:${port}" | |
| info "Press Ctrl+C to stop" | |
| echo "" | |
| cd "${PROJECT_ROOT}" | |
| "${YARN_BIN}" docusaurus serve --host "${host}" --port "${port}" | |
| } | |
| cmd_clean() { | |
| step "Cleaning build artifacts and caches" | |
| cd "${PROJECT_ROOT}" | |
| local dirs_to_clean=("build" ".docusaurus" "node_modules/.cache") | |
| for d in "${dirs_to_clean[@]}"; do | |
| if [[ -d "${d}" ]]; then | |
| info "Removing ${d}/" | |
| rm -rf "${d}" | |
| fi | |
| done | |
| ok "Clean completed" | |
| info "Run './local_dev.sh install' to reinstall dependencies if needed." | |
| } | |
| cmd_install() { | |
| validate_env | |
| do_install | |
| } | |
| cmd_help() { | |
| # Print the header comments of this script | |
| sed -n '/^##*$/,/^##*$/p' "${BASH_SOURCE[0]}" | head -40 | |
| echo "" | |
| echo -e "${BOLD}Quick start:${NC}" | |
| echo " ./local_dev.sh # Start English dev server" | |
| echo " ./local_dev.sh start-zh # Start Chinese dev server" | |
| echo " ./local_dev.sh build # Full build (en only, default)" | |
| echo " ./local_dev.sh build-all # Full build (en + zh-CN)" | |
| echo " ./local_dev.sh build-docs-next # docs-next dev server (hot reload)" | |
| echo "" | |
| } | |
| # ─── Parse arguments ───────────────────────────────────────── | |
| COMMAND="${1:-start}" | |
| shift 2>/dev/null || true | |
| OPT_PORT="3000" | |
| OPT_HOST="localhost" | |
| OPT_SKIP_INSTALL="false" | |
| OPT_VERSIONS="current" | |
| OPT_MAX_MEM="2048" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --port) | |
| OPT_PORT="$2"; shift 2 ;; | |
| --host) | |
| OPT_HOST="$2"; shift 2 ;; | |
| --skip-install) | |
| OPT_SKIP_INSTALL="true"; shift ;; | |
| --versions) | |
| OPT_VERSIONS="$2"; shift 2 ;; | |
| --max-mem) | |
| OPT_MAX_MEM="$2"; shift 2 ;; | |
| -h|--help) | |
| COMMAND="help"; shift ;; | |
| *) | |
| error "Unknown option: $1" | |
| cmd_help | |
| exit 1 ;; | |
| esac | |
| done | |
| # ─── Dispatch command ──────────────────────────────────────── | |
| case "${COMMAND}" in | |
| start) cmd_start ;; | |
| start-landing) cmd_start_landing ;; | |
| start-zh) cmd_start_zh ;; | |
| build) cmd_build "en" ;; | |
| build-all) cmd_build "en zh-CN" ;; | |
| build-en) cmd_build "en" ;; | |
| build-docs-next) cmd_build_docs_next_only ;; | |
| build-docs-next-all) cmd_build_docs_next_all ;; | |
| serve) cmd_serve ;; | |
| install) cmd_install ;; | |
| clean) cmd_clean ;; | |
| help|-h|--help) cmd_help ;; | |
| *) | |
| error "Unknown command: ${COMMAND}" | |
| cmd_help | |
| exit 1 ;; | |
| esac |