diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 00000000..c058fe66 --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,84 @@ +# 生产打包 + +`san deploy`是远程部署命令,与 `san build --remote` 相比,支持更复杂的远程目标配置,可通过 `san-cli` 集成也可通过安装 `san-deploy-cli` 独立使用。 + +## 使用命令 + +```bash +san deploy [target] +``` + +- target:推送目标,具体传输路径由 `san.deploy.config.js` 指定。 + +### 配置相关 + +`san.deploy.config.js`的内容是一个 Node.js 的 CommandJS 格式,默认配置是:` + +- `--config -c` 指定配置文件,默认读取项目根目录下的 san.deploy.config.js +- `--watch -w` 开启监听模式,默认不开启,仅推送一次 + +### 配置文件 + +以 `san deploy test` 命令为例,推送至 test指定的远程环境。san.deploy.config.js 文件内容如下: + +```js +module.exports = { + test: { + root: 'output', + disableFsr: false, + ignore: [/(^|[\/\\])\../, '**/node_modules/**'], + host: 'http://machine.com:8999', + receiver: '', + rule: [ + { + match: '**', // glob + to: '/path/to/dest', + }, + { + match: ['output/**', 'template/**'], + to: '/path/to/dest' + } + ], + replace: { // object or array + from: new RegExp('http://static.com', 'ig'), // string/reg + to: 'http://dev.com:8888/' + } + } +}; +``` + +#### `root` +监听的根目录,可省略,默认 "."(当前目录) + +#### `ignore` +忽略的文件,值为 `string` or `array`, 符合 [anymatch](https://www.npmjs.com/package/anymatch) 规范 + +#### `receiver` +远程服务的 receiver.php 地址,receiver.php 文件内容[参考](https://github.com/fex-team/fis3-deploy-http-push/blob/master/receiver.php) + +#### `disableFsr` +是否禁用 fsr 安全部署服务,值为 true 或 false,默认是 false ,使用 fsr 安全部署服务。 + +若远端机器使用脚本等方式接收,须禁用 fsr ,将此项置为 true ) + +#### `host` +配置此项的前提是,disableFsr 为 false,启用了 fsr 安全部署服务,用于替换原来的 reciever 配置,拼接成该此项设置的域名。 + +#### `rule` +部署规则,值为 `object` or `array`, 其中每一项内,指定了文件匹配规则 match, 部署到远端的 路径 to。 +match 值为 string or array,支持 glob匹配文件 规则。 + +#### `replace` +替换规则,值为 `object` or `array`, 通常用于将静态文件 cdn 替换为测试环境静态服务地址。其中每一项需指定:原字符(from,支持正则及 string),替换的目标字符(to) + +### 执行部署 + +```bash +# 单次构建并远程部署 +san deploy test +# 监听产出每次变动自动执行远程部署 +san build test --watch +``` + +> 实现依赖 [deploy-files](https://github.com/wanwu/deploy-files) 的 upload.js文件 +> 一些不适于安装 san cli 的工程可使用 [san-deploy-cli](https://www.npmjs.com/package/san-deploy-cli) 完成推送。 diff --git a/packages/san-cli-build/package.json b/packages/san-cli-build/package.json index 61baa59c..9f1eaee9 100644 --- a/packages/san-cli-build/package.json +++ b/packages/san-cli-build/package.json @@ -1,7 +1,7 @@ { "name": "san-cli-build", "description": "San-CLI build command", - "version": "2.1.1", + "version": "2.1.3", "main": "index.js", "license": "MIT", "engines": { diff --git a/packages/san-cli-deploy/.npmignore b/packages/san-cli-deploy/.npmignore new file mode 100644 index 00000000..cd950691 --- /dev/null +++ b/packages/san-cli-deploy/.npmignore @@ -0,0 +1,3 @@ +__tests__ +docs/** +node_modules/** diff --git a/packages/san-cli-deploy/README.md b/packages/san-cli-deploy/README.md new file mode 100644 index 00000000..63a62b7d --- /dev/null +++ b/packages/san-cli-deploy/README.md @@ -0,0 +1,24 @@ +# san-cli-deploy + +san-cli-deploy 是 [San](https://github.com/baidu/san) CLI 工具中远程部署功能,与san build --remote +相比,支持更复杂的远程目标配置,可通过 `san-cli` 集成也可通过安装 `san-deploy-cli` 独立使用。 +san deploy 命令的执行主体。 + +## 使用文档 + +请移步[San-CLI 文档](https://ecomfe.github.io/san-cli) + +## 安装 + +```shell +$ npm install --save-dev san-cli-deploy +``` + +## 测试 + +执行命令 + +```bash +#执行__tests__下所有测试文件 +yarn test +``` diff --git a/packages/san-cli-deploy/index.js b/packages/san-cli-deploy/index.js new file mode 100644 index 00000000..4ea8095e --- /dev/null +++ b/packages/san-cli-deploy/index.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) Baidu Inc. All rights reserved. + * + * This source code is licensed under the MIT license. + * See LICENSE file in the project root for license information. + * + * @file deploy + */ + +const {logger, chalk} = require('san-cli-utils/ttyLogger'); + +exports.command = 'deploy [target]'; +exports.description = 'Send file to the remote target machine'; +exports.builder = { + watch: { + alias: 'w', + type: 'boolean', + default: false, + describe: 'Watch mode' + }, + config: { + alias: 'c', + type: 'string', + describe: 'Deploy config file' + } +}; + +exports.handler = argv => { + if (!argv.target) { + logger.error(chalk.red('Deploy target not found, please set [target]')); + return; + } + require('./run')(argv); +}; diff --git a/packages/san-cli-deploy/package.json b/packages/san-cli-deploy/package.json new file mode 100644 index 00000000..b1d240c4 --- /dev/null +++ b/packages/san-cli-deploy/package.json @@ -0,0 +1,32 @@ +{ + "name": "san-cli-deploy", + "description": "San-CLI deploy command", + "version": "1.0.2", + "main": "index.js", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + }, + "author": "zttonly", + "repository": { + "type": "git", + "url": "git+https://github.com/ecomfe/san-cli.git", + "directory": "packages/san-cli-deploy" + }, + "bugs": { + "url": "https://github.com/ecomfe/san-cli/issues" + }, + "homepage": "https://ecomfe.github.io/san-cli", + "keywords": [ + "san", + "deploy", + "san.js", + "san-cli" + ], + "dependencies": { + "chokidar": "^3.5.3", + "deploy-files": "^0.2.6", + "graceful-fs": "^4.2.10", + "san-cli-utils": "^1.0.5" + } +} diff --git a/packages/san-cli-deploy/run.js b/packages/san-cli-deploy/run.js new file mode 100644 index 00000000..294f657c --- /dev/null +++ b/packages/san-cli-deploy/run.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) Baidu Inc. All rights reserved. + * + * This source code is licensed under the MIT license. + * See LICENSE file in the project root for license information. + * + * @file deploy + * @author zttonly + */ + +const {resolve, isAbsolute} = require('path'); +const fs = require('graceful-fs'); +const chokidar = require('chokidar'); + +const {logger} = require('san-cli-utils/ttyLogger'); +const {textCommonColor} = require('san-cli-utils/color'); +const {findExisting} = require('san-cli-utils/path'); +const Upload = require('deploy-files/upload'); + +const loadConfig = (filepath, cwd) => { + // 读取远程目标配置 + let configFile = filepath; + let config = null; + if (configFile && typeof configFile === 'string') { + configFile = isAbsolute(configFile) ? configFile : resolve(cwd, configFile); + if (!fs.existsSync(configFile)) { + configFile = false; + logger.warn(`Config file \`${filepath}\` is not exists!`); + } + } + + if (!configFile) { + // 主动查找 cwd 目录的config + configFile = findExisting(['san.deploy.config.js', '.san.deploy.config.js'], cwd); + } + + if (configFile) { + config = require(configFile); + + if (typeof config !== 'object') { + logger.error(`${textCommonColor(configFile)}: Expected object type.`); + } + } + return config; +}; + +const arrify = (value = []) => (Array.isArray(value) ? value : [value]); + +module.exports = function apply(argv) { + const cwd = process.cwd(); + const config = loadConfig(argv.config, cwd); + if (!config) { + logger.error('Please set default deploy configuration!'); + return; + } + + if (!argv.target || !config[argv.target]) { + logger.error('Deploy target invalid!'); + return; + } + const deployObj = config[argv.target]; + const { + root = '.', + disableFsr = false, + ignore = [/(^|[\/\\])\../, '**/node_modules/**'], + host, + receiver, + watchOptions = {} + } = deployObj; + + const up = new Upload({ + disableFsr, + host, + receiver, + replace: arrify(deployObj.replace), + }); + const rule = arrify(deployObj.rule); + rule.forEach(item => { + let timer = null; + const watcher = chokidar.watch(item.match, { + cwd: resolve(cwd, root), + ignored: ignore, // glob + persistent: argv.watch, + ...watchOptions + }); + const files = {}; + watcher.on('all', (event, file) => { + const filePth = resolve(cwd, root, file); + if (event === 'add' || event === 'change') { + files[file] = fs.readFileSync(filePth); + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + up.upload({ + to: item.to, + files + }); + timer = null; + }, 500); + } + }); + }); +};