@liuli-util/cli 3.17.2 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,11 +22,11 @@ export interface BaseDeployOptions {
22
22
  debug: boolean;
23
23
  type: DeployTypeEnum;
24
24
  }
25
- export declare type SftpDeployOptions = Omit<BaseDeployOptions, 'type'> & {
25
+ export interface SftpDeployOptions extends Omit<BaseDeployOptions, 'type'> {
26
+ dist: string;
26
27
  dest: string;
27
- remote: string;
28
28
  sshConfig: ConnectOptions;
29
- };
29
+ }
30
30
  /**
31
31
  * sftp 集成到远端
32
32
  */
@@ -36,10 +36,28 @@ export declare class SftpDeployService implements IDeployService {
36
36
  deploy(): EventExtPromise<void, DeployEvents>;
37
37
  validate(): [isValidate: boolean, errorText: string];
38
38
  }
39
- export declare type GhPagesDeployOptions = Omit<BaseDeployOptions, 'type'> & {
40
- dest: string;
41
- remote: string;
42
- };
39
+ export interface GhPagesDeployOptions extends Omit<BaseDeployOptions, 'type'> {
40
+ /**
41
+ * 推送的本地目录
42
+ */
43
+ dist: string;
44
+ /**
45
+ * 推送的远端目录,默认为分支根目录
46
+ */
47
+ dest?: string;
48
+ /**
49
+ * 推送的项目 git 地址,默认为当前项目
50
+ */
51
+ repo?: string;
52
+ /**
53
+ * 推送的远端,默认为 origin
54
+ */
55
+ remote?: string;
56
+ /**
57
+ * 远端分支名,默认为 gh-pages
58
+ */
59
+ branch?: string;
60
+ }
43
61
  /**
44
62
  * 将本地静态资源推送到 gh-pages 远端
45
63
  */
@@ -1 +1 @@
1
- {"version":3,"file":"DeployService.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy/DeployService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAe,MAAM,oBAAoB,CAAA;AACjE,OAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAUzD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAgBD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;IACpD;;OAEG;IACH,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;CAC9C;AAED,oBAAY,cAAc;IACxB,OAAO,aAAa;IACpB,IAAI,SAAS;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,oBAAY,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG;IAChE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,cAAc,CAAA;CAC1B,CAAA;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEvD,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC;IAc7C,QAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC;CAqBrD;AAED,oBAAY,oBAAoB,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAE1D,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC;IAgD7C,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC;CAclD"}
1
+ {"version":3,"file":"DeployService.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy/DeployService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAe,MAAM,oBAAoB,CAAA;AACjE,OAAe,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAWzD,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;IACpD;;OAEG;IACH,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;CAC9C;AAED,oBAAY,cAAc;IACxB,OAAO,aAAa;IACpB,IAAI,SAAS;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,cAAc,CAAA;CAC1B;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,iBAAiB;IAEvD,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC;IAc7C,QAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC;CAsBrD;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC;IAC3E;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,oBAAoB;IAE1D,MAAM,IAAI,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC;IA8E7C,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC;CAkBlD"}
@@ -0,0 +1,8 @@
1
+ import { JSONSchemaType } from 'ajv';
2
+ /**
3
+ * 使用 ajv 校验数据
4
+ * @param schema json 模式配置
5
+ * @param data 校验的数据
6
+ */
7
+ export declare function validate<T>(schema: JSONSchemaType<T>, data: T): [isValid: boolean, errorText: string];
8
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/commands/deploy/util/validate.ts"],"names":[],"mappings":"AAAA,OAAY,EAAE,cAAc,EAAE,MAAM,KAAK,CAAA;AAIzC;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAQrG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liuli-util/cli",
3
- "version": "3.17.2",
3
+ "version": "3.18.0",
4
4
  "description": "一个针对于库和 CLI 应用程序打包的零配置 CLI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",
@@ -25,6 +25,7 @@
25
25
  "@liuli-util/eslint-config-ts": "^0.2.0",
26
26
  "@liuli-util/prettier-standard-config": "^0.1.0",
27
27
  "ajv": "^8.8.2",
28
+ "ajv-formats": "^2.1.1",
28
29
  "ajv-i18n": "^4.2.0",
29
30
  "chokidar": "^3.5.2",
30
31
  "commander": "^8.2.0",
@@ -69,5 +70,5 @@
69
70
  "dev": "esno src/bin.ts build cli -w",
70
71
  "start": "esno src/bin.ts"
71
72
  },
72
- "readme": "# @liuli-util/cli\r\n\r\n一个针对于库和 CLI 应用程序打包的零配置 CLI。\r\n\r\n## 起步\r\n\r\n### 安装\r\n\r\n```sh\r\nyarn add -D @liuli-util/cli # 局部安装\r\nnpm i -g @liuli-util/cli # 全局安装\r\n```\r\n\r\n### 打包\r\n\r\n```sh\r\nyarn liuli-cli build lib # 打包库\r\nyarn liuli-cli build cli # 打包 cli 引用程序\r\n```\r\n\r\n> 添加 `-w` 选项则启动 rollup 的监视模式,打包出来的 dist/ 不会压缩且不会将依赖项打进 bundle。\r\n\r\n![监视模式](https://liuli.dev/images/liuli-cli%20%E7%9B%91%E8%A7%86%E6%A8%A1%E5%BC%8F.gif)\r\n\r\n### 生成\r\n\r\n```sh\r\nyarn liuli-cli generate <name> --template lib # 生成 ts-lib 项目\r\nyarn liuli-cli generate <name> --template cli # 生成 cli 项目\r\n```\r\n\r\nutil 也支持交互式的创建项目\r\n\r\n```shell\r\nyarn liuli-cli generate\r\n```\r\n\r\n![liuli-cli 交互式创建截图](https://liuli.dev/images/liuli-cli%20%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%88%9B%E5%BB%BA%E6%88%AA%E5%9B%BE.gif)\r\n\r\n### 同步配置\r\n\r\n```shell\r\nyarn liuli-cli sync\r\n```\r\n\r\n需要在 package.json 中指定同步哪些配置\r\n\r\n```json\r\n{\r\n \"sync\": [\"prettier\", \"workspaces\", \"commitlint\", \"simplehooks\"]\r\n}\r\n```\r\n\r\n目前支持的配置项\r\n\r\n- prettier\r\n- commitlint\r\n- simplehooks\r\n- workspaces\r\n- gitignore\r\n- eslint-ts\r\n- eslint-vue-ts\r\n- jest\r\n\r\n未来的目标:默认将包括检查 cli 自身的同步(如果需要在 monorepo 之外使用的话),eslint/style-lint 之类,还有在没有配置时实现交互式 cli\r\n\r\n> 注:目前仅同步依赖而不会执行安装\r\n\r\n也支持交互式的初始化同步配置\r\n\r\n```shell\r\nyarn liuli-cli sync init\r\n```\r\n\r\n## 设计理念\r\n\r\n- 约定大于配置,如果可能应该不提供配置。VitePress 也是这样做的,参考:https://vitepress.vuejs.org/#lighter-page-weight 这会导致一些约束,包括以下内容\r\n - 打包库时入口文件必须是 `src/index.ts`,出口文件则是 `dist/index.esm.js` `dist/index.js`\r\n - 打包 CLI 时入口文件必须是 `src/bin.ts`,出口文件则是 `dist/bin.js`\r\n - 在打包 lib 时会将所有的依赖作为外部依赖处理,而在打包 cli 时会将所有依赖项打进 bundle\r\n\r\n## FAQ\r\n\r\n### 为什么底层没有选择 esbuild\r\n\r\n事实上,esbuild 本身非常非常非常快(重要的事情说三遍),但如果使用 js 封装 CLI,则性能会迅速降低。\r\n\r\n打包这个项目使用 esbuild、cli 封装、rollup 的时间对比如下\r\n\r\n| 打包方式 | 时间 |\r\n| -------- | ----- |\r\n| esbuild | 0.49s |\r\n| cli 封装 | 3.2s |\r\n| rollup | 3.65s |\r\n\r\n> 现在 [vscode 插件打包官方推荐使用 esbuild](https://code.visualstudio.com/api/working-with-extensions/bundling-extension) ,吾辈在生产项目中也有过实践,长期而言吾辈比较看好这类更高性能的打包工具。\r\n\r\n### 为什么不捆绑外部依赖项\r\n\r\n主要原因是希望将捆绑的工作交由最终应用完成,避免重复捆绑相同的依赖,而且还可以避免处理 nodejs 中直接基于文件系统使用 `worker_threads` 的问题。\r\n"
73
+ "readme": "# @liuli-util/cli\r\n\r\n> [中文](https://github.com/rxliuli/liuli-tools/tree/master/apps/liuli-cli/README.zh-CN.md)\r\n\r\nA zero-configuration CLI packaged for libraries and CLI applications.\r\n\r\n## Getting started\r\n\r\n### Install\r\n\r\n```sh\r\nyarn add -D @liuli-util/cli # local installation\r\nnpm i -g @liuli-util/cli # install globally\r\n```\r\n\r\n### Bale\r\n\r\n```sh\r\nyarn liuli-cli build lib # package library\r\nyarn liuli-cli build cli # package cli reference program\r\n```\r\n\r\n> Add the `-w` option to start the watch mode of rollup, the packaged dist/ will not be compressed and the dependencies will not be included in the bundle.\r\n\r\n![Monitor Mode](https://liuli.dev/images/liuli-cli%20%E7%9B%91%E8%A7%86%E6%A8%A1%E5%BC%8F.gif)\r\n\r\n### Generate\r\n\r\n```sh\r\nyarn liuli-cli generate <name> --template lib # Generate ts-lib project\r\nyarn liuli-cli generate <name> --template cli # Generate cli project\r\n```\r\n\r\nutil also supports interactive project creation\r\n\r\n```shell\r\nyarn liuli-cli generate\r\n```\r\n\r\n![Liuli-cli interactively create screenshots](https://liuli.dev/images/liuli-cli%20%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%88%9B %E5%BB%BA%E6%88%AA%E5%9B%BE.gif)\r\n\r\n### Sync configuration\r\n\r\n```shell\r\nyarn liuli-cli sync\r\n```\r\n\r\nWhich configuration needs to be synced in package.json\r\n\r\n```json\r\n{\r\n \"sync\": [\"prettier\", \"workspaces\", \"commitlint\", \"simplehooks\"]\r\n}\r\n```\r\n\r\nCurrently supported configuration items\r\n\r\n- prettier\r\n- commitlint\r\n- simplehooks\r\n- workspaces\r\n- gitignore\r\n- eslint-ts\r\n- eslint-vue-ts\r\n- jest\r\n\r\nFuture goals: By default will include checking the synchronization of the cli itself (if it needs to be used outside of a monorepo), eslint/style-lint etc., and implementing an interactive cli when not configured\r\n\r\n> Note: Currently only the dependencies are synced and no installation is performed\r\n\r\nInteractive initialization synchronization configuration is also supported\r\n\r\n```shell\r\nyarn liuli-cli sync init\r\n```\r\n\r\n## design concept\r\n\r\n- Convention over configuration, configuration should not be provided if possible. VitePress does this too, reference: https://vitepress.vuejs.org/#lighter-page-weight This leads to some constraints, including the following\r\n - When packaging the library, the entry file must be `src/index.ts`, and the export file must be `dist/index.esm.js` and `dist/index.js`\r\n - When packaging the CLI, the entry file must be `src/bin.ts`, and the exit file must be `dist/bin.js`\r\n - All dependencies will be treated as external dependencies when packaging lib, and all dependencies will be bundled when packaging cli\r\n\r\n## FAQ\r\n\r\n### Why not bundle external dependencies\r\n\r\nThe main reason is that you want to leave the bundling work to the final application, avoid bundling the same dependencies repeatedly, and also avoid dealing with the problem of using `worker_threads` directly based on the file system in nodejs.\r\n"
73
74
  }
@@ -8,25 +8,12 @@ import localize from 'ajv-i18n/localize'
8
8
  import simpleGit from 'simple-git'
9
9
  import { nodeCacheDir } from '../../utils/nodeCacheDir'
10
10
  import { performance, PerformanceObserver } from 'perf_hooks'
11
+ import { validate } from './util/validate'
11
12
 
12
13
  export interface DeployEvents {
13
14
  process(title: string): void
14
15
  }
15
16
 
16
- /**
17
- * 使用 ajv 校验数据
18
- * @param schema
19
- * @param data
20
- */
21
- function ajvValidate(schema: object, data: object): [isValid: boolean, errorText: string] {
22
- const ajv = new Ajv({ allErrors: true, messages: false })
23
- const res = ajv.validate(schema, data)
24
- if (!res) {
25
- localize.zh(ajv.errors)
26
- }
27
- return [res, ajv.errorsText()]
28
- }
29
-
30
17
  /**
31
18
  * 部署服务接口
32
19
  */
@@ -49,9 +36,9 @@ export interface BaseDeployOptions {
49
36
  type: DeployTypeEnum
50
37
  }
51
38
 
52
- export type SftpDeployOptions = Omit<BaseDeployOptions, 'type'> & {
39
+ export interface SftpDeployOptions extends Omit<BaseDeployOptions, 'type'> {
40
+ dist: string
53
41
  dest: string
54
- remote: string
55
42
  sshConfig: ConnectOptions
56
43
  }
57
44
 
@@ -69,38 +56,57 @@ export class SftpDeployService implements IDeployService {
69
56
  ...this.options.sshConfig,
70
57
  privateKey,
71
58
  })
72
- await client.mkdir(this.options.remote, true)
73
- await client.uploadDir(path.resolve(this.options.cwd, this.options.dest), this.options.remote)
59
+ await client.mkdir(this.options.dest, true)
60
+ await client.uploadDir(path.resolve(this.options.cwd, this.options.dist), this.options.dest)
74
61
  await client.end()
75
62
  })
76
63
  }
77
64
 
78
65
  validate(): [isValidate: boolean, errorText: string] {
79
- return ajvValidate(
66
+ return validate<SftpDeployOptions>(
80
67
  {
81
68
  type: 'object',
82
69
  properties: {
70
+ debug: { type: 'boolean' },
83
71
  cwd: { type: 'string' },
72
+ dist: { type: 'string' },
84
73
  dest: { type: 'string' },
85
- remote: { type: 'string' },
86
74
  sshConfig: {
87
75
  type: 'object',
88
76
  properties: {
89
77
  host: { type: 'string' },
90
78
  username: { type: 'string' },
91
79
  },
92
- },
80
+ } as any,
93
81
  },
94
- required: ['cwd', 'dest', 'remote', 'sshConfig'],
82
+ required: ['cwd', 'dist', 'dest', 'sshConfig', 'debug'],
95
83
  },
96
84
  this.options,
97
85
  )
98
86
  }
99
87
  }
100
88
 
101
- export type GhPagesDeployOptions = Omit<BaseDeployOptions, 'type'> & {
102
- dest: string
103
- remote: string
89
+ export interface GhPagesDeployOptions extends Omit<BaseDeployOptions, 'type'> {
90
+ /**
91
+ * 推送的本地目录
92
+ */
93
+ dist: string
94
+ /**
95
+ * 推送的远端目录,默认为分支根目录
96
+ */
97
+ dest?: string
98
+ /**
99
+ * 推送的项目 git 地址,默认为当前项目
100
+ */
101
+ repo?: string
102
+ /**
103
+ * 推送的远端,默认为 origin
104
+ */
105
+ remote?: string
106
+ /**
107
+ * 远端分支名,默认为 gh-pages
108
+ */
109
+ branch?: string
104
110
  }
105
111
 
106
112
  /**
@@ -110,10 +116,10 @@ export class GhPagesDeployService implements IDeployService {
110
116
  constructor(private readonly options: GhPagesDeployOptions) {}
111
117
 
112
118
  deploy(): EventExtPromise<void, DeployEvents> {
113
- const defaultRemote = 'origin'
114
- const defaultBranch = 'gh-pages'
119
+ const defaultRemote = this.options.remote ?? 'origin'
120
+ const defaultBranch = this.options.branch ?? 'gh-pages'
115
121
  return PromiseUtil.wrapOnEvent(async (events: DeployEvents) => {
116
- const obs = new PerformanceObserver((perfObserverList, observer) => {
122
+ const obs = new PerformanceObserver((perfObserverList) => {
117
123
  if (this.options.debug) {
118
124
  console.log(perfObserverList.getEntries())
119
125
  }
@@ -126,25 +132,48 @@ export class GhPagesDeployService implements IDeployService {
126
132
  }
127
133
  mark('开始推送')
128
134
  const git = simpleGit(this.options.cwd)
129
- mark('获取当前项目的远端配置')
130
- const originRemote = (await git.getRemotes(true)).find((item) => item.name === defaultRemote)
131
- if (!originRemote) {
132
- throw new Error('当前目录不是一个 git 项目或没有配置 git remote')
135
+ if (!this.options.repo) {
136
+ mark('获取当前项目的远端配置')
137
+ const originRemote = (await git.getRemotes(true)).find((item) => item.name === defaultRemote)
138
+ if (!originRemote) {
139
+ throw new Error('当前目录不是一个 git 项目或没有配置 git remote')
140
+ }
141
+ this.options.repo = originRemote.refs.fetch
133
142
  }
134
143
  const ghPagesRoot = path.resolve(nodeCacheDir('liuli-cli'), 'gh-pages')
135
- const originRepoName = originRemote.refs.fetch.replace(new RegExp('[/:]', 'g'), '_')
144
+ const originRepoName = this.options.repo.replace(new RegExp('[/:]', 'g'), '_')
136
145
  const localRepoPath = path.resolve(ghPagesRoot, originRepoName)
146
+ let forcePush = false
137
147
  if (!(await pathExists(localRepoPath))) {
138
148
  mark('克隆项目')
139
- await git.clone(originRemote.refs.fetch, localRepoPath, { '--branch': defaultBranch })
149
+ //如果失败,应该尝试克隆主分支,然后 checkout
150
+ try {
151
+ await git.clone(this.options.repo, localRepoPath, {
152
+ '--branch': defaultBranch,
153
+ '--single-branch': null,
154
+ '--depth': 1,
155
+ })
156
+ } catch (e) {
157
+ mark(`未找到 ${defaultBranch} 分支,尝试自动创建`)
158
+ await git.clone(this.options.repo, localRepoPath, {
159
+ '--single-branch': null,
160
+ '--depth': 1,
161
+ })
162
+ await git.cwd(localRepoPath)
163
+ await git.checkout({
164
+ '--orphan': defaultBranch,
165
+ })
166
+ forcePush = true
167
+ }
140
168
  } else {
169
+ await git.cwd(localRepoPath)
141
170
  mark('更新项目')
142
171
  await git.pull()
143
172
  }
144
173
  mark('复制文件')
145
- const remoteDestPath = path.join(localRepoPath, this.options.remote)
174
+ const remoteDestPath = path.join(localRepoPath, this.options.dest ?? './')
146
175
  await mkdirp(remoteDestPath)
147
- await copy(path.resolve(this.options.cwd, this.options.dest), remoteDestPath)
176
+ await copy(path.resolve(this.options.cwd, this.options.dist), remoteDestPath)
148
177
  mark('提交所有文件')
149
178
  await git.cwd(localRepoPath)
150
179
  await git.add('-A')
@@ -154,26 +183,30 @@ export class GhPagesDeployService implements IDeployService {
154
183
  await git.commit('Updates gh-pages by liuli-cli')
155
184
  }
156
185
  mark('推送到远端')
157
- if ((await git.status()).ahead === 0) {
158
- mark('没有更新,跳过推送')
159
- } else {
186
+ if ((await git.status()).ahead > 0 || forcePush) {
160
187
  await git.push(defaultRemote, defaultBranch)
161
188
  mark('完成推送')
189
+ } else {
190
+ mark('没有更新,跳过推送')
162
191
  }
163
192
  obs.disconnect()
164
193
  })
165
194
  }
166
195
 
167
196
  validate(): [isValid: boolean, errorText: string] {
168
- return ajvValidate(
197
+ return validate<GhPagesDeployOptions>(
169
198
  {
170
199
  type: 'object',
171
200
  properties: {
201
+ debug: { type: 'boolean' },
172
202
  cwd: { type: 'string' },
173
- dest: { type: 'string' },
174
- remote: { type: 'string' },
203
+ dist: { type: 'string' },
204
+ dest: { type: 'string', nullable: true },
205
+ repo: { type: 'string', nullable: true },
206
+ remote: { type: 'string', nullable: true },
207
+ branch: { type: 'string', nullable: true },
175
208
  },
176
- required: ['cwd', 'dest', 'remote'],
209
+ required: ['debug', 'cwd', 'dist'],
177
210
  },
178
211
  this.options,
179
212
  )
@@ -1,7 +1,8 @@
1
1
  import * as path from 'path'
2
- import { mkdirp, remove, writeFile } from 'fs-extra'
3
- import { GhPagesDeployService, SftpDeployOptions, SftpDeployService } from '../DeployService'
2
+ import { mkdirp, remove, unlink, writeFile } from 'fs-extra'
3
+ import { GhPagesDeployOptions, GhPagesDeployService, SftpDeployOptions, SftpDeployService } from '../DeployService'
4
4
  import { execPromise } from '../util/execPromise'
5
+ import { nodeCacheDir } from '../../../utils/nodeCacheDir'
5
6
 
6
7
  const tempPath = path.resolve(__dirname, '.temp')
7
8
  beforeAll(async () => {
@@ -25,14 +26,15 @@ beforeAll(async () => {
25
26
  )
26
27
  })
27
28
 
28
- describe('测试 SftpDeployService', () => {
29
+ describe.skip('测试 SftpDeployService', () => {
29
30
  const options: SftpDeployOptions = {
31
+ debug: false,
30
32
  cwd: tempPath,
31
- dest: 'dist',
32
- remote: '/home/pinefield/apps/test',
33
+ dist: 'dist',
34
+ dest: process.env.dest!,
33
35
  sshConfig: {
34
- host: '10.8.2.4',
35
- username: 'pinefield',
36
+ host: process.env.host,
37
+ username: process.env.username,
36
38
  },
37
39
  }
38
40
  it('基本示例', async () => {
@@ -55,21 +57,52 @@ describe('测试 SftpDeployService', () => {
55
57
  })
56
58
 
57
59
  describe('测试 GhPagesDeployService', () => {
58
- const ghPagesDeployService = new GhPagesDeployService({
60
+ const options: GhPagesDeployOptions = {
61
+ debug: false,
59
62
  cwd: tempPath,
60
- dest: 'dist',
61
- remote: 'examples/test-app',
63
+ dist: 'dist',
64
+ dest: '/',
65
+ repo: 'https://github.com/rxliuli/test-git.git',
66
+ remote: 'origin',
67
+ branch: 'gh-pages',
68
+ }
69
+ const ghPagesDeployService = new GhPagesDeployService(options)
70
+ const distPath = path.resolve(tempPath, 'dist')
71
+ const mock = jest.fn().mockImplementation((title) => console.log(title))
72
+ beforeEach(async () => {
73
+ await writeFile(
74
+ path.resolve(distPath, 'index.html'),
75
+ `<!DOCTYPE html>
76
+ <html lang="en">
77
+ <head>
78
+ <meta charset="UTF-8" />
79
+ <title>测试部署</title>
80
+ </head>
81
+ <body>
82
+ <h1>测试部署</h1>
83
+ <p>${Date.now()}</p>
84
+ </body>
85
+ </html>
86
+ `,
87
+ )
88
+ mock.mockClear()
62
89
  })
63
90
  it('基本示例', async () => {
64
- await ghPagesDeployService.deploy().on('process', (title) => console.log(title))
91
+ await ghPagesDeployService.deploy().on('process', mock)
92
+ console.log(mock.mock.calls)
93
+ expect(mock.mock.calls.some((item: string[]) => item.includes('完成推送'))).toBeTruthy()
65
94
  }, 10_000)
66
- //TODO 无法使用单元测试
67
- it.skip('并发推送', async () => {
68
- const scriptPath = path.resolve(__dirname, './util/deployGhPageWorker.ts').replace(/\\/g, '/')
69
- await Promise.all(
70
- [1, 2].map(async () => {
71
- await execPromise(`esno ${scriptPath}`)
72
- }),
73
- )
95
+ it('测试没有任何修改', async () => {
96
+ await writeFile(path.resolve(distPath, 'index.html'), 'test')
97
+ await ghPagesDeployService.deploy().on('process', mock)
98
+ await ghPagesDeployService.deploy().on('process', mock)
99
+ expect(mock.mock.calls.some((item: string[]) => item.includes('没有任何提交,跳过提交'))).toBeTruthy()
100
+ expect(mock.mock.calls.some((item: string[]) => item.includes('没有更新,跳过推送'))).toBeTruthy()
101
+ }, 20_000)
102
+ it('测试首次拉取代码', async () => {
103
+ const dir = path.resolve(nodeCacheDir('liuli-cli'), 'gh-pages', options.repo!.replace(new RegExp('[/:]', 'g'), '_'))
104
+ await remove(dir)
105
+ await ghPagesDeployService.deploy().on('process', mock)
106
+ expect(mock.mock.calls.some((item: string[]) => item.includes('克隆项目'))).toBeTruthy()
74
107
  }, 10_000)
75
108
  })
@@ -0,0 +1,18 @@
1
+ import Ajv, { JSONSchemaType } from 'ajv'
2
+ import localize from 'ajv-i18n/localize'
3
+ import formatsPlugin from 'ajv-formats'
4
+
5
+ /**
6
+ * 使用 ajv 校验数据
7
+ * @param schema json 模式配置
8
+ * @param data 校验的数据
9
+ */
10
+ export function validate<T>(schema: JSONSchemaType<T>, data: T): [isValid: boolean, errorText: string] {
11
+ const ajv = new Ajv({ allErrors: true, messages: false })
12
+ formatsPlugin(ajv)
13
+ const res = ajv.validate(schema, data)
14
+ if (!res) {
15
+ localize.zh(ajv.errors)
16
+ }
17
+ return [res, ajv.errorsText()]
18
+ }
@@ -13,18 +13,14 @@
13
13
  "bin": {
14
14
  "cli-name": "./bin.js"
15
15
  },
16
- "jest": {
17
- "preset": "ts-jest"
18
- },
19
16
  "dependencies": {
20
17
  "commander": "^8.2.0",
21
18
  "enquirer": "^2.3.6",
22
19
  "fs-extra": "^10.0.0"
23
20
  },
24
21
  "devDependencies": {
25
- "@liuli-util/cli": "workspace:*",
22
+ "@liuli-util/cli": "^3.18.0",
26
23
  "@types/fs-extra": "^9.0.13",
27
- "@types/inquirer": "^8.1.2",
28
24
  "@types/jest": "^27.0.2",
29
25
  "@types/lodash": "^4.14.173",
30
26
  "@types/node": "^16.9.6",
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander'
2
- import { prompt } from 'inquirer'
2
+ import { prompt } from 'enquirer'
3
3
 
4
4
  new Command()
5
5
  .action(async () => {
@@ -7,19 +7,12 @@
7
7
  "types": "dist/index.d.ts",
8
8
  "license": "MIT",
9
9
  "scripts": {
10
- "build": "rimraf dist && liuli-cli build pkg",
11
- "dev": "liuli-cli build pkg -w"
12
- },
13
- "jest": {
14
- "preset": "ts-jest"
10
+ "build": "rimraf dist && liuli-cli build lib",
11
+ "dev": "liuli-cli build lib -w"
15
12
  },
16
13
  "devDependencies": {
17
- "@liuli-util/cli": "workspace:*",
18
- "@types/jest": "^27.0.2",
19
- "esno": "^0.9.1",
20
- "jest": "^27.2.1",
14
+ "@liuli-util/cli": "^3.18.0",
21
15
  "rimraf": "^3.0.2",
22
- "ts-jest": "^27.0.5",
23
16
  "type-fest": "^2.3.4",
24
17
  "typescript": "^4.4.3"
25
18
  }