採用fabric自動部署前端專案

語言: CN / TW / HK

本文已參與「新人創作禮」活動,一起開啟掘金創作之路。

背景:

前端專案採用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命令可用)

$cat fabfile.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/

  1. then open git bash and run the following command: $ fab pre
  2. For linux
  3. 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 = "[email protected]" 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 = "[email protected]" scp_to_server(folder, host=host) c = make_connection(host=host) deploy(c, folder) print("success to deploy the static files at production server.") ```