本文已参与「新人创作礼」活动,一起开启掘金创作之路。

背景:

前端项目采用Nginx部署在云上

server {
    server_name www.domain.com;
    client_max_body_size 10m;
    location /chairong/dist {
      root /home/user/frontend;
      try_files $uri $uri/ /project-name/dist/index.html;
    }
    location = / {
      rewrite (.*) project-name/dist permanent;
    }
    ...
}

本地安装了python3.8+和fabric(如果是windows系统需配置一下,让fab命令可用)

$catpython123 fabfi服务器价格le.py # 该文件放在前端代码仓库里,与package.json同级目录

__author__ = "WJ"
"""
Auto update server deploy
~~~~~~~~~~~~~~~~~~~~~~~~~
前端`output_dir`包自动部署脚本
自动将yarn pre构建出来的`output_dir`文件夹,替换掉测试服务器上的旧文件夹
Usage:
- For windows
1. Install python3.8
2. pip install fabric
3. add folder of fab.exe to environment path
# Example: /c/Users/Administrator/AppData/Roaming/Python/Python38/Scripts/
4. then open git bash and run the following command:
    $ fab pre
- For linux
1. just run:
    $ cd /path/to/project && (which fab||pip install fabric --user) && fab dist
"""
import os
from datetime import datetime
from os.path import getmtime
from pathlib import Path
from typing import Union
from fabric import Connection, task
PROJECT = Path().resolve().name  # 假设当前文件夹名就是项目名
HOST = "user@www.domain.com"
PATH = f"~/frontend/{PROJECT}"   # 服务器上dist包放置的位置
DELTA = 60 * 1  # 这个时间内(1 min),如果output_dir已存在,不再重新yarn build
DEPLOY_DIR = "dist"  # 服务器上部署的文件夹名
OUTPUT_DIR = "bundle"  # yarn pre生成的文件夹名
def parse_args(domain=None, host=None, port: int = 22):
    if host is None:
        if domain is None:
            host = HOST
        else:
            user = os.getenv(f"{domain}_user".replace(".", "_"))
            host = f"{user}@{domain}"
    if domain is None:
        domain = host.split("@")[-1]
    if (p := os.getenv(f"{domain}_port")) :  # noqa: E231, E203
        port = int(p)  # type: ignore
    return domain, host, port
def make_connection(domain=None, host=None, port: int = 22):
    domain, host, port = parse_args(domain, host, port)
    passwd = os.getenv(f"{domain}_passwd")
    c = Connection(host, port=port, connect_kwargs={"password": passwd})
    print(f"Success to make a connection with {host}")
    return c
def seconds_from_last_updated(fpath: Union[str, Path]) -> int:
    """return the seconds passed after `fpath` last motified"""
    mtime = datetime.fromtimestamp(getmtime(fpath))
    passed = datetime.now() - mtime
    return passed.seconds
def run_and_echo(cmd: str, run_func=None) -> int:
    """运行cmd,并打印运行的语句"""
    print("-->", cmd)
    if run_func is None:
        run_func = os.system
    return run_func(cmd)
def build_compiled_dir(action: str = "pre", verbose: str = "") -> None:
    """自动给yarn管理的项目编译出output_dir文件夹"""
    actions = [i for i in (action, verbose) if i]
    cmds = [
        f"{tool} {action}"
        for tool in ("yarn", "npm run")
        for action in actions
    ]
    for cmd in cmds:
        if run_and_echo(cmd) == 0:
            break
def zip_output_dir(folder: str) -> None:
    """如果output_dir文件夹不存在,则编译后用zip压缩,否则直接压缩"""
    actions = ("pre",) if folder == "bundle" else ("b", "build")
    if not (p := Path(folder)).exists():  # noqa: E231, E203
        build_compiled_dir(*actions)
    elif seconds_from_last_updated(p) > DELTA:
        run_and_echo(f"rm -rf {p}")
        build_compiled_dir(*actions)
    run_and_echo(f"zip -qr {folder}.zip {folder}/")
def create_or_fresh_zip(folder: str = OUTPUT_DIR) -> str:
    p = Path(f"{folder}.zip")
    if not p.exists():
        zip_output_dir(folder)
    elif seconds_from_last_updated(p) > DELTA:
        run_and_echo(f"mv {folder}.zip {folder}.zip.bak")
        zip_output_dir(folder)
    return folder
def scp_to_server(
    folder: str,
    domain: Union[str, None] = None,
    port: int = 22,
    host: Union[str, None] = None,
    path: str = PATH,
) -> None:
    _, host, port = parse_args(domain, host, port)
    run_and_echo(f"scp -P {port} {folder}.zip {host}:{path}")
def deploy(c, folder: str):
    deploy_dir = DEPLOY_DIR
    with c.cd(PATH):
        run_and_echo(f"rm -rf {deploy_dir}", c.run)
        # c.run("rm -rf dist.zip")
        # c.put("./dist.zip", ".")
        run_and_echo(f"unzip -q {folder}.zip", c.run)
        if deploy_dir != folder:
            run_and_echo(f"mv {folder} {deploy_dir}", c.run)
@task
def pre(c):
    """自动部署前端yarn pre出来的文件夹到测试服务器"""
    folder = create_or_fresh_zip()
    scp_to_server(folder)
    c = make_connection()
    deploy(c, folder)
    print("success to deploy the static files")
@task
def es(c):
    """自动部署前端yarn build出来的文件夹到生产服务器"""
    folder = create_or_fresh_zip("dist")
    domain = "www.domain-of-production-server.com"
    scp_to_server(folder, domain)
    c = make_connection(domain)
    deploy(c, folder)
    print("success to deploy the static files at production server.")
@task
def dev(c):
    """自动部署前端yarn f出来的文件夹到内部服务器"""
    folder = create_or_fresh_zip("dist")
    host = "dev@dev.esoaru.com"
    scp_to_server(folder, host=host)
    c = make_connection(host=host)
    deploy(c, folder)
    print("success to deploy the static files at production server.")