diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0992525 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +scripts/dev.sh text eol=lf diff --git a/.gitignore b/.gitignore index dc88a1b..55f042d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ logs/ temp/* !temp/keep.txt .idea +.dev/ # *.py \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index d7219cd..591c020 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,6 +47,12 @@ Directories named `archive` under `barcode-reader/{mobile,server,web}/` hold his If you find yourself wanting to *add* content to an archive directory, it almost certainly belongs in the live directory instead. +## Testing the local site + +The Jekyll layout comes from `Docs-Template-Repo`, not this repository. Install Git and Ruby/Bundler plus `rsync` (Bash) or `robocopy` (PowerShell). From the repo root, run `./scripts/dev.sh` or `./scripts/dev.ps1` to prepare `.dev/DocHome` and serve at `http://localhost:5555/faq/`. The scripts exclude archived FAQ directories and local worktrees from that merged workspace. + +For preparation without a server, use `./scripts/dev.sh --no-serve` or `./scripts/dev.ps1 -NoServe`; rerun the script without that flag to start the server. Use `--no-template-update` / `-NoTemplateUpdate` to reuse the cloned template. Rerun after editing FAQ source because the merged workspace is a copy, then check the changed page in the local site. `.dev/` is generated and Git-ignored; do not edit it as source. + ## Before finishing Run the link checker from the repo root and fix anything it flags in files you touched: diff --git a/README.md b/README.md index 1fc04f5..d37240b 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,24 @@ See [`AGENTS.md`](AGENTS.md) for the FAQ article structure, frontmatter, linking The site is built with Jekyll using a shared theme/layout maintained in [dynamsoft-docs/Docs-Template-Repo](https://github.com/dynamsoft-docs/Docs-Template-Repo), which this repo doesn't include locally. Pushes to `main` and `preview` trigger the CI workflows in `.github/workflows/main.yml`, which build and sync to production and the preview/testing environment respectively. +For a local preview, install Git, Ruby/Bundler, and rsync (Bash) or robocopy (PowerShell), then run from the repository root: + +```bash +./scripts/dev.sh +``` + +Or on Windows: + +```powershell +./scripts/dev.ps1 +``` + +The scripts clone the shared template's preview branch into `.dev/`, merge it with this FAQ source, install gems, and serve Jekyll at `http://localhost:5555/faq/`. The generated `.dev/` workspace is ignored by Git and omits archived FAQ content and local worktrees. Use `--no-serve` / `-NoServe` to prepare without serving, then rerun without that flag to serve; `--no-template-update` / `-NoTemplateUpdate` reuses the cloned template. Changes to FAQ files require rerunning the script to refresh the merged workspace. + ## Checking links `check_links.py` crawls the repo's Markdown files and reports broken links. Run it before submitting a change that touches links: ```bash python check_links.py -``` +``` \ No newline at end of file diff --git a/check_links.py b/check_links.py index ce54653..798579b 100644 --- a/check_links.py +++ b/check_links.py @@ -26,6 +26,7 @@ def find_md_files(root_dir): for root, dirs, files in os.walk(root_dir): if '.git' in dirs: dirs.remove('.git') if '_site' in dirs: dirs.remove('_site') + if '.dev' in dirs: dirs.remove('.dev') for file in files: if file.endswith('.md'): diff --git a/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md b/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md deleted file mode 120000 index 4904f82..0000000 --- a/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md +++ /dev/null @@ -1 +0,0 @@ -../../barcode-reader/web/camera/camera-preview-renders-incorrectly-ios.md \ No newline at end of file diff --git a/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md b/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md new file mode 100644 index 0000000..4904f82 --- /dev/null +++ b/mrz-scanner/general/camera-preview-renders-incorrectly-ios.md @@ -0,0 +1 @@ +../../barcode-reader/web/camera/camera-preview-renders-incorrectly-ios.md \ No newline at end of file diff --git a/scripts/dev.ps1 b/scripts/dev.ps1 new file mode 100644 index 0000000..0d5abd5 --- /dev/null +++ b/scripts/dev.ps1 @@ -0,0 +1,159 @@ +[CmdletBinding()] +param( + [string]$TemplateRepoUrl = "https://github.com/dynamsoft-docs/Docs-Template-Repo.git", + [string]$TemplateBranch = "preview", + [int]$Port = 5555, + [string]$BindHost = "localhost", + [switch]$NoTemplateUpdate, + [switch]$NoServe +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Write-Step { + param([string]$Message) + Write-Host "==> $Message" +} + +function Ensure-Command { + param([string]$Name) + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "Required command '$Name' was not found in PATH." + } +} + +function Invoke-Native { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath, + [string[]]$Arguments = @() + ) + + & $FilePath @Arguments + $code = $LASTEXITCODE + if ($code -ne 0) { + $argText = ($Arguments -join " ") + throw "Command failed with exit code ${code}: $FilePath $argText" + } +} + +function Invoke-Robocopy { + param( + [string]$Source, + [string]$Destination, + [string[]]$ExtraArgs + ) + + $baseArgs = @( + $Source, + $Destination, + "/R:2", + "/W:2", + "/NFL", + "/NDL", + "/NJH", + "/NJS", + "/NP" + ) + $allArgs = $baseArgs + $ExtraArgs + & robocopy @allArgs | Out-Null + $code = $LASTEXITCODE + if ($code -ge 8) { + throw "robocopy failed with exit code $code. Source: $Source Destination: $Destination" + } +} + +Ensure-Command "git" +Ensure-Command "robocopy" +Ensure-Command "bundle" + +$repoRoot = Split-Path -Parent $PSScriptRoot +$devRoot = Join-Path $repoRoot ".dev" +$templateRoot = Join-Path $devRoot "Docs-Template-Repo" +$docHome = Join-Path $devRoot "DocHome" +$bundlePath = Join-Path $devRoot "vendor\bundle" +$bundleConfig = Join-Path $devRoot ".bundle" +$bundleUserHome = Join-Path $devRoot ".bundle-user" +$jekyllCache = Join-Path $devRoot ".jekyll-cache" +$siteDir = Join-Path $devRoot "_site" + +Write-Step "Preparing local workspace in $devRoot" +New-Item -ItemType Directory -Force -Path $devRoot | Out-Null + +if (-not (Test-Path $templateRoot)) { + Write-Step "Cloning template repo ($TemplateBranch)" + Invoke-Native -FilePath "git" -Arguments @("clone", "--depth", "1", "--branch", $TemplateBranch, $TemplateRepoUrl, $templateRoot) +} elseif (-not $NoTemplateUpdate) { + Write-Step "Syncing template repo ($TemplateBranch)" + Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "fetch", "origin", $TemplateBranch, "--depth", "1") + Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "reset", "--hard") + Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "clean", "-fd") + Invoke-Native -FilePath "git" -Arguments @("-C", $templateRoot, "checkout", "-B", $TemplateBranch, "origin/$TemplateBranch") +} else { + Write-Step "Skipping template update" +} + +Write-Step "Rebuilding merged site workspace" +if (Test-Path $docHome) { + Remove-Item -LiteralPath $docHome -Recurse -Force +} +New-Item -ItemType Directory -Force -Path $docHome | Out-Null +Invoke-Robocopy -Source $repoRoot -Destination $docHome -ExtraArgs @( + "/MIR", + "/XD", ".git", ".dev", ".claude", ".vs", "node_modules", "_site", ".bundle", ".jekyll-cache", ".sass-cache", "vendor", + (Join-Path $repoRoot "barcode-reader\mobile\archive"), + (Join-Path $repoRoot "barcode-reader\server\archive"), + (Join-Path $repoRoot "barcode-reader\web\archive") +) +Invoke-Robocopy -Source $templateRoot -Destination $docHome -ExtraArgs @( + "/E", + "/XD", ".git", ".github", "_site", "node_modules" +) + +Write-Step "Applying local assetsPath replacements" +$search = "assetsPath = '/webres/wwwroot'" +$replace = "assetsPath = 'https://www.dynamsoft.com/webres/wwwroot'" +foreach ($dirName in @("_includes", "_layouts")) { + $dirPath = Join-Path $docHome $dirName + if (-not (Test-Path $dirPath)) { + continue + } + + Get-ChildItem -Path $dirPath -Recurse -File | ForEach-Object { + $path = $_.FullName + try { + $content = [System.IO.File]::ReadAllText($path) + } catch { + return + } + if ($content.Contains($search)) { + $updated = $content.Replace($search, $replace) + [System.IO.File]::WriteAllText($path, $updated) + } + } +} + +Write-Step "Configuring Bundler and Jekyll local paths" +$env:BUNDLE_PATH = $bundlePath +$env:BUNDLE_APP_CONFIG = $bundleConfig +$env:BUNDLE_USER_HOME = $bundleUserHome +$env:BUNDLE_USER_CACHE = Join-Path $bundleUserHome "cache" +$env:JEKYLL_CACHE_DIR = $jekyllCache +$env:JEKYLL_ENV = "development" +New-Item -ItemType Directory -Force -Path $bundlePath, $bundleConfig, $bundleUserHome, $jekyllCache, $siteDir | Out-Null + +Push-Location $docHome +try { + Write-Step "Installing Ruby dependencies" + Invoke-Native -FilePath "bundle" -Arguments @("install") + + if ($NoServe) { + Write-Step "Workspace prepared. Run this script again without -NoServe to start Jekyll." + } else { + Write-Step "Starting Jekyll server on port $Port" + Invoke-Native -FilePath "bundle" -Arguments @("exec", "jekyll", "serve", "-P", "$Port", "--trace", "--host=$BindHost", "--livereload", "--destination", "$siteDir") + } +} finally { + Pop-Location +} diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..6ba3f39 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +set -euo pipefail + +template_repo_url="https://github.com/dynamsoft-docs/Docs-Template-Repo.git" +template_branch="preview" +port="5555" +bind_host="localhost" +no_template_update="false" +no_serve="false" + +write_step() { + echo "==> $1" +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Required command '$1' was not found in PATH." >&2 + exit 1 + fi +} + +show_help() { + cat <<'EOF' +Usage: ./scripts/dev.sh [options] + +Options: + --template-repo-url URL Template repository URL + --template-branch NAME Template branch (default: preview) + --port N Jekyll port (default: 5555) + --bind-host HOST Jekyll bind host (default: localhost) + --no-template-update Skip template fetch/pull + --no-serve Prepare workspace only (do not start Jekyll) + -h, --help Show this help message +EOF +} + +require_option_value() { + if [[ $# -lt 2 || -z "${2:-}" || "${2:-}" == --* ]]; then + echo "Missing value for option: $1" >&2 + show_help + exit 1 + fi +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --template-repo-url) + require_option_value "$@" + template_repo_url="$2" + shift 2 + ;; + --template-branch) + require_option_value "$@" + template_branch="$2" + shift 2 + ;; + --port) + require_option_value "$@" + port="$2" + shift 2 + ;; + --bind-host) + require_option_value "$@" + bind_host="$2" + shift 2 + ;; + --no-template-update) + no_template_update="true" + shift + ;; + --no-serve) + no_serve="true" + shift + ;; + -h|--help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + show_help + exit 1 + ;; + esac +done + +require_cmd git +require_cmd rsync +require_cmd bundle +require_cmd find +require_cmd sed + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "$script_dir/.." && pwd)" +dev_root="$repo_root/.dev" +template_root="$dev_root/Docs-Template-Repo" +doc_home="$dev_root/DocHome" +bundle_path="$dev_root/vendor/bundle" +bundle_config="$dev_root/.bundle" +bundle_user_home="$dev_root/.bundle-user" +jekyll_cache="$dev_root/.jekyll-cache" +site_dir="$dev_root/_site" + +write_step "Preparing local workspace in $dev_root" +mkdir -p "$dev_root" + +if [[ ! -d "$template_root/.git" ]]; then + write_step "Cloning template repo ($template_branch)" + git clone --depth 1 --branch "$template_branch" "$template_repo_url" "$template_root" +elif [[ "$no_template_update" != "true" ]]; then + write_step "Syncing template repo ($template_branch)" + git -C "$template_root" fetch origin "$template_branch" --depth 1 + git -C "$template_root" reset --hard + git -C "$template_root" clean -fd + git -C "$template_root" checkout -B "$template_branch" "origin/$template_branch" +else + write_step "Skipping template update" +fi + +write_step "Rebuilding merged site workspace" +rm -rf "$doc_home" +mkdir -p "$doc_home" +rsync -a --delete \ + --exclude '.git' \ + --exclude '.dev' \ + --exclude '.claude' \ + --exclude '/barcode-reader/mobile/archive/' \ + --exclude '/barcode-reader/server/archive/' \ + --exclude '/barcode-reader/web/archive/' \ + --exclude '.vs' \ + --exclude 'node_modules' \ + --exclude '_site' \ + --exclude '.bundle' \ + --exclude '.jekyll-cache' \ + --exclude '.sass-cache' \ + --exclude 'vendor' \ + "$repo_root"/ "$doc_home"/ +rsync -a \ + --exclude '.git' \ + --exclude '.github' \ + --exclude '_site' \ + --exclude 'node_modules' \ + "$template_root"/ "$doc_home"/ + +write_step "Applying local assetsPath replacements" +search="assetsPath = '/webres/wwwroot'" +replace="assetsPath = 'https://www.dynamsoft.com/webres/wwwroot'" +for dir_name in "_includes" "_layouts"; do + dir_path="$doc_home/$dir_name" + if [[ ! -d "$dir_path" ]]; then + continue + fi + + while IFS= read -r -d '' file_path; do + sed -i.bak "s|$search|$replace|g" "$file_path" + rm -f -- "$file_path.bak" + done < <(find "$dir_path" -type f -print0) +done + +write_step "Configuring Bundler and Jekyll local paths" +export BUNDLE_PATH="$bundle_path" +export BUNDLE_APP_CONFIG="$bundle_config" +export BUNDLE_USER_HOME="$bundle_user_home" +export BUNDLE_USER_CACHE="$bundle_user_home/cache" +export JEKYLL_CACHE_DIR="$jekyll_cache" +export JEKYLL_ENV="development" +mkdir -p "$bundle_path" "$bundle_config" "$bundle_user_home" "$jekyll_cache" "$site_dir" + +pushd "$doc_home" >/dev/null +write_step "Installing Ruby dependencies" +bundle install + +if [[ "$no_serve" == "true" ]]; then + write_step "Workspace prepared. Run this script again without --no-serve to start Jekyll." +else + write_step "Starting Jekyll server on port $port" + bundle exec jekyll serve -P "$port" --trace --host="$bind_host" --livereload --destination "$site_dir" +fi +popd >/dev/null