feat(webpage): add deploy, pdf subcommands and build --serve option
Migrate mywebpage/bin/mywebpage CLI features into mytoolkit: - deploy: auto-infer GIT_USER from gh CLI or git origin - pdf build/open: delegate to project's scripts/export-docs-pdf.py - build --serve: serve built site after successful build
This commit is contained in:
+178
-8
@@ -1,11 +1,14 @@
|
|||||||
"""Webpage project utilities (Docusaurus build / clear / etc.).
|
"""Webpage project utilities (Docusaurus build / clear / deploy / pdf / etc.).
|
||||||
|
|
||||||
Project metadata lives in `server.py` so that adding a new webpage there
|
Project metadata lives in `server.py` so that adding a new webpage there
|
||||||
automatically wires it up here.
|
automatically wires it up here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -67,13 +70,6 @@ def webpage_list():
|
|||||||
click.echo("")
|
click.echo("")
|
||||||
|
|
||||||
|
|
||||||
@webpage_cmd.command("build")
|
|
||||||
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
|
||||||
def webpage_build(name):
|
|
||||||
"""Build a webpage project (`npm run build`)."""
|
|
||||||
_run_npm(name, "build", "Building")
|
|
||||||
|
|
||||||
|
|
||||||
@webpage_cmd.command("clear")
|
@webpage_cmd.command("clear")
|
||||||
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
||||||
def webpage_clear(name):
|
def webpage_clear(name):
|
||||||
@@ -87,3 +83,177 @@ def webpage_rebuild(name):
|
|||||||
"""Clear cache then build (`npm run clear && npm run build`)."""
|
"""Clear cache then build (`npm run clear && npm run build`)."""
|
||||||
_run_npm(name, "clear", "Clearing")
|
_run_npm(name, "clear", "Clearing")
|
||||||
_run_npm(name, "build", "Building")
|
_run_npm(name, "build", "Building")
|
||||||
|
|
||||||
|
|
||||||
|
@webpage_cmd.command("build")
|
||||||
|
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
||||||
|
@click.option("--serve", "-s", is_flag=True, help="Serve the built site after successful build")
|
||||||
|
@click.option("--port", "-p", type=int, help="Port for serve (default: Docusaurus default)")
|
||||||
|
def webpage_build_serve(name, serve, port):
|
||||||
|
"""Build a webpage project (`npm run build`). Use --serve to start serving."""
|
||||||
|
svc = get_service(name)
|
||||||
|
assert svc.project_dir is not None
|
||||||
|
|
||||||
|
_run_npm(name, "build", "Building")
|
||||||
|
|
||||||
|
if serve:
|
||||||
|
click.secho(f"Serving {svc.name} ...", fg="cyan")
|
||||||
|
cmd = ["npx", "docusaurus", "serve", "--no-open"]
|
||||||
|
if port is not None:
|
||||||
|
cmd += ["--port", str(port)]
|
||||||
|
proc = subprocess.run(cmd, cwd=svc.project_dir, env=no_color_env())
|
||||||
|
if proc.returncode != 0:
|
||||||
|
click.secho(f"Serve failed for {svc.name}", fg="red")
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
|
||||||
|
@webpage_cmd.command("deploy")
|
||||||
|
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
||||||
|
@click.argument("deploy_args", nargs=-1)
|
||||||
|
def webpage_deploy(name, deploy_args):
|
||||||
|
"""Deploy a webpage project to GitHub Pages (`npm run deploy`).
|
||||||
|
|
||||||
|
Automatically infers GIT_USER from `gh` CLI or git origin URL.
|
||||||
|
"""
|
||||||
|
svc = get_service(name)
|
||||||
|
assert svc.project_dir is not None
|
||||||
|
|
||||||
|
if not svc.project_dir.exists():
|
||||||
|
click.secho(f"Project directory not found: {svc.project_dir}", fg="red")
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
env = no_color_env()
|
||||||
|
|
||||||
|
# Infer GIT_USER if not already set
|
||||||
|
if not env.get("GIT_USER"):
|
||||||
|
use_ssh = env.get("USE_SSH", "").lower() == "true"
|
||||||
|
if not use_ssh:
|
||||||
|
login = ""
|
||||||
|
# Try gh CLI
|
||||||
|
gh_path = shutil.which("gh")
|
||||||
|
if gh_path:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[gh_path, "api", "user", "--jq", ".login"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
login = result.stdout.strip()
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fallback to git origin URL
|
||||||
|
if not login:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(svc.project_dir), "remote", "get-url", "origin"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
origin = result.stdout.strip()
|
||||||
|
m = re.search(r"github\.com[:/]([^/]+)/", origin)
|
||||||
|
if m:
|
||||||
|
login = m.group(1)
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if login:
|
||||||
|
env["GIT_USER"] = login
|
||||||
|
click.secho(f"Inferred GIT_USER={login}", fg="bright_black")
|
||||||
|
|
||||||
|
click.secho(f"Deploying {svc.name} ({svc.description}) ...", fg="cyan")
|
||||||
|
cmd = ["npm", "run", "deploy"]
|
||||||
|
if deploy_args:
|
||||||
|
cmd.append("--")
|
||||||
|
cmd.extend(deploy_args)
|
||||||
|
proc = subprocess.run(cmd, cwd=svc.project_dir, env=env)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
click.secho(f"Deploy failed for {svc.name}", fg="red")
|
||||||
|
raise click.Abort()
|
||||||
|
click.secho(f"{svc.name} deployed.", fg="green")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# PDF subcommand
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@click.group(name="pdf", invoke_without_command=False)
|
||||||
|
def pdf_cmd():
|
||||||
|
"""Export docs to PDF or open existing PDF."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pdf_cmd.command("build")
|
||||||
|
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
||||||
|
@click.option("-o", "--output", help="Output PDF path")
|
||||||
|
@click.option("-t", "--title", help="Cover page title")
|
||||||
|
@click.option("-s", "--subtitle", help="Cover page subtitle")
|
||||||
|
@click.option("-d", "--date", help="Cover page date")
|
||||||
|
@click.option("-l", "--lang", type=click.Choice(["zh", "en", "both"]), help="Language (mywebpage only)")
|
||||||
|
def pdf_build(name, output, title, subtitle, date, lang):
|
||||||
|
"""Export docs to PDF via project's scripts/export-docs-pdf.py."""
|
||||||
|
svc = get_service(name)
|
||||||
|
assert svc.project_dir is not None
|
||||||
|
|
||||||
|
script = svc.project_dir / "scripts" / "export-docs-pdf.py"
|
||||||
|
if not script.exists():
|
||||||
|
click.secho(f"PDF export script not found: {script}", fg="red")
|
||||||
|
click.secho("Expected: scripts/export-docs-pdf.py in the project directory.", fg="yellow")
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
cmd = ["python3", str(script)]
|
||||||
|
if output:
|
||||||
|
cmd += ["--output", output]
|
||||||
|
if title:
|
||||||
|
cmd += ["--title", title]
|
||||||
|
if subtitle:
|
||||||
|
cmd += ["--subtitle", subtitle]
|
||||||
|
if date:
|
||||||
|
cmd += ["--date", date]
|
||||||
|
if lang:
|
||||||
|
cmd += ["--lang", lang]
|
||||||
|
|
||||||
|
click.secho(f"Exporting PDF for {svc.name} ...", fg="cyan")
|
||||||
|
proc = subprocess.run(cmd, cwd=svc.project_dir, env=no_color_env())
|
||||||
|
if proc.returncode != 0:
|
||||||
|
click.secho(f"PDF export failed for {svc.name}", fg="red")
|
||||||
|
raise click.Abort()
|
||||||
|
click.secho(f"{svc.name} PDF exported.", fg="green")
|
||||||
|
|
||||||
|
|
||||||
|
@pdf_cmd.command("open")
|
||||||
|
@click.argument("name", type=click.Choice(_NAMES, case_sensitive=False))
|
||||||
|
@click.option("-o", "--output", help="PDF path to open")
|
||||||
|
def pdf_open(name, output):
|
||||||
|
"""Open the exported PDF with the default application."""
|
||||||
|
svc = get_service(name)
|
||||||
|
assert svc.project_dir is not None
|
||||||
|
|
||||||
|
if output:
|
||||||
|
pdf_path = svc.project_dir / output
|
||||||
|
else:
|
||||||
|
# Try to infer default path from the export script's default
|
||||||
|
script = svc.project_dir / "scripts" / "export-docs-pdf.py"
|
||||||
|
pdf_path = None
|
||||||
|
if script.exists():
|
||||||
|
content = script.read_text(encoding="utf-8")
|
||||||
|
m = re.search(r'(?:DEFAULT_OUTPUT|default)\s*=\s*["\']([^"\']+)["\']', content)
|
||||||
|
if m:
|
||||||
|
pdf_path = svc.project_dir / m.group(1)
|
||||||
|
if pdf_path is None:
|
||||||
|
pdf_path = svc.project_dir / "static" / "pdf" / f"{svc.name}-docs.pdf"
|
||||||
|
|
||||||
|
if not pdf_path.exists():
|
||||||
|
click.secho(f"PDF not found: {pdf_path}", fg="red")
|
||||||
|
click.secho("Run 'mytoolkit webpage pdf build <name>' first.", fg="yellow")
|
||||||
|
raise click.Abort()
|
||||||
|
|
||||||
|
click.secho(f"Opening {pdf_path} ...", fg="cyan")
|
||||||
|
subprocess.run(["open", str(pdf_path)], check=False)
|
||||||
|
|
||||||
|
|
||||||
|
webpage_cmd.add_command(pdf_cmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user