@liuli-util/cli 3.16.0 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +30 -42
  4. package/README.zh-CN.md +86 -0
  5. package/dist/bin.js +72 -72
  6. package/dist/bin.js.map +3 -3
  7. package/dist/commands/deploy/DeployService.d.ts +26 -7
  8. package/dist/commands/deploy/DeployService.d.ts.map +1 -1
  9. package/dist/commands/deploy/index.d.ts.map +1 -1
  10. package/dist/commands/deploy/util/validate.d.ts +8 -0
  11. package/dist/commands/deploy/util/validate.d.ts.map +1 -0
  12. package/package.json +74 -72
  13. package/src/commands/deploy/DeployService.ts +103 -46
  14. package/src/commands/deploy/__tests__/DeployService.test.ts +52 -19
  15. package/src/commands/deploy/__tests__/simpleGit.test.ts +26 -1
  16. package/src/commands/deploy/index.ts +2 -1
  17. package/src/commands/deploy/util/validate.ts +18 -0
  18. package/src/commands/sync/__tests__/.temp/package.json +16 -0
  19. package/templates/cli/package.json +1 -5
  20. package/templates/cli/src/bin.ts +1 -1
  21. package/templates/cli/tsconfig.json +28 -28
  22. package/templates/lib/package.json +3 -10
  23. package/templates/lib/tsconfig.json +28 -28
  24. package/tsconfig.json +34 -34
  25. package/src/commands/esbuild/__tests__/.temp/getDeps/package.json +0 -1
  26. package/src/commands/generate/__tests__/.temp/test-cli/CHANGELOG.md +0 -1
  27. package/src/commands/generate/__tests__/.temp/test-cli/README.md +0 -1
  28. package/src/commands/generate/__tests__/.temp/test-cli/bin.js +0 -3
  29. package/src/commands/generate/__tests__/.temp/test-cli/package.json +0 -44
  30. package/src/commands/generate/__tests__/.temp/test-cli/src/bin.ts +0 -13
  31. package/src/commands/generate/__tests__/.temp/test-cli/src/index.ts +0 -1
  32. package/src/commands/generate/__tests__/.temp/test-cli/tsconfig.json +0 -28
@@ -19,13 +19,14 @@ export declare enum DeployTypeEnum {
19
19
  }
20
20
  export interface BaseDeployOptions {
21
21
  cwd: string;
22
+ debug: boolean;
22
23
  type: DeployTypeEnum;
23
24
  }
24
- export declare type SftpDeployOptions = Omit<BaseDeployOptions, 'type'> & {
25
+ export interface SftpDeployOptions extends Omit<BaseDeployOptions, 'type'> {
26
+ dist: string;
25
27
  dest: string;
26
- remote: string;
27
28
  sshConfig: ConnectOptions;
28
- };
29
+ }
29
30
  /**
30
31
  * sftp 集成到远端
31
32
  */
@@ -35,10 +36,28 @@ export declare class SftpDeployService implements IDeployService {
35
36
  deploy(): EventExtPromise<void, DeployEvents>;
36
37
  validate(): [isValidate: boolean, errorText: string];
37
38
  }
38
- export declare type GhPagesDeployOptions = Omit<BaseDeployOptions, 'type'> & {
39
- dest: string;
40
- remote: string;
41
- };
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
+ }
42
61
  /**
43
62
  * 将本地静态资源推送到 gh-pages 远端
44
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;AASzD,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,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;IAiC7C,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAInC,eAAO,MAAM,aAAa,SAEsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAInC,eAAO,MAAM,aAAa,SAG0E,CAAA"}
@@ -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,72 +1,74 @@
1
- {
2
- "name": "@liuli-util/cli",
3
- "version": "3.16.0",
4
- "description": "一个针对于库和 CLI 应用程序打包的零配置 CLI",
5
- "main": "dist/index.js",
6
- "module": "dist/index.esm.js",
7
- "types": "dist/index.d.ts",
8
- "license": "MIT",
9
- "scripts": {
10
- "setup": "pnpm build",
11
- "build": "rimraf dist && pnpm start build cli",
12
- "dev": "esno src/bin.ts build cli -w",
13
- "start": "esno src/bin.ts"
14
- },
15
- "bin": {
16
- "liuli-cli": "./bin.js"
17
- },
18
- "jest": {
19
- "preset": "ts-jest",
20
- "moduleNameMapper": {
21
- "lodash-es": "lodash"
22
- }
23
- },
24
- "publishConfig": {
25
- "access": "public",
26
- "registry": "https://registry.npmjs.org/"
27
- },
28
- "dependencies": {
29
- "@liuli-util/commitlint-standard-config": "^0.1.6",
30
- "@liuli-util/eslint-config-react-ts": "^0.1.0",
31
- "@liuli-util/eslint-config-ts": "^0.2.0",
32
- "@liuli-util/prettier-standard-config": "^0.1.0",
33
- "ajv": "^8.8.2",
34
- "ajv-i18n": "^4.2.0",
35
- "chokidar": "^3.5.2",
36
- "commander": "^8.2.0",
37
- "conf": "^10.1.1",
38
- "enquirer": "^2.3.6",
39
- "esbuild": "^0.13.2",
40
- "fs-extra": "^10.0.0",
41
- "glob": "^7.2.0",
42
- "glob-promise": "^4.2.0",
43
- "json5": "^2.2.0",
44
- "lodash-es": "^4.17.21",
45
- "simple-git": "^2.45.1",
46
- "spinnies": "^0.5.1",
47
- "ssh2": "^1.5.0",
48
- "ssh2-sftp-client": "^7.2.1",
49
- "ts-morph": "^12.0.0"
50
- },
51
- "devDependencies": {
52
- "@types/find-cache-dir": "^3.2.1",
53
- "@types/fs-extra": "^9.0.13",
54
- "@types/glob": "^7",
55
- "@types/jest": "^27.0.2",
56
- "@types/lodash-es": "^4.17.5",
57
- "@types/node": "^16.9.6",
58
- "@types/ssh2-sftp-client": "^7.0.0",
59
- "comlink": "^4.3.1",
60
- "esno": "^0.9.1",
61
- "jest": "^27.2.1",
62
- "lodash": "^4.17.21",
63
- "rimraf": "^3.0.2",
64
- "ts-jest": "^27.0.5",
65
- "type-fest": "^2.3.4",
66
- "typescript": "^4.4.3"
67
- },
68
- "repository": {
69
- "type": "git",
70
- "url": "https://github.com/rxliuli/liuli-tools/tree/master/apps/liuli-cli"
71
- }
72
- }
1
+ {
2
+ "name": "@liuli-util/cli",
3
+ "version": "3.18.0",
4
+ "description": "一个针对于库和 CLI 应用程序打包的零配置 CLI",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "license": "MIT",
9
+ "bin": {
10
+ "liuli-cli": "./bin.js"
11
+ },
12
+ "jest": {
13
+ "preset": "ts-jest",
14
+ "moduleNameMapper": {
15
+ "lodash-es": "lodash"
16
+ }
17
+ },
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org/"
21
+ },
22
+ "dependencies": {
23
+ "@liuli-util/commitlint-standard-config": "^0.1.6",
24
+ "@liuli-util/eslint-config-react-ts": "^0.1.0",
25
+ "@liuli-util/eslint-config-ts": "^0.2.0",
26
+ "@liuli-util/prettier-standard-config": "^0.1.0",
27
+ "ajv": "^8.8.2",
28
+ "ajv-formats": "^2.1.1",
29
+ "ajv-i18n": "^4.2.0",
30
+ "chokidar": "^3.5.2",
31
+ "commander": "^8.2.0",
32
+ "conf": "^10.1.1",
33
+ "enquirer": "^2.3.6",
34
+ "esbuild": "^0.13.2",
35
+ "fs-extra": "^10.0.0",
36
+ "glob": "^7.2.0",
37
+ "glob-promise": "^4.2.0",
38
+ "json5": "^2.2.0",
39
+ "lodash-es": "^4.17.21",
40
+ "simple-git": "^2.45.1",
41
+ "spinnies": "^0.5.1",
42
+ "ssh2": "^1.5.0",
43
+ "ssh2-sftp-client": "^7.2.1",
44
+ "ts-morph": "^12.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/find-cache-dir": "^3.2.1",
48
+ "@types/fs-extra": "^9.0.13",
49
+ "@types/glob": "^7",
50
+ "@types/jest": "^27.0.2",
51
+ "@types/lodash-es": "^4.17.5",
52
+ "@types/node": "^16.9.6",
53
+ "@types/ssh2-sftp-client": "^7.0.0",
54
+ "comlink": "^4.3.1",
55
+ "esno": "^0.9.1",
56
+ "jest": "^27.2.1",
57
+ "lodash": "^4.17.21",
58
+ "rimraf": "^3.0.2",
59
+ "ts-jest": "^27.0.5",
60
+ "type-fest": "^2.3.4",
61
+ "typescript": "^4.4.3"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "https://github.com/rxliuli/liuli-tools/tree/master/apps/liuli-cli"
66
+ },
67
+ "scripts": {
68
+ "setup": "pnpm build",
69
+ "build": "rimraf dist && pnpm start build cli",
70
+ "dev": "esno src/bin.ts build cli -w",
71
+ "start": "esno src/bin.ts"
72
+ },
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"
74
+ }
@@ -7,25 +7,13 @@ import Ajv from 'ajv'
7
7
  import localize from 'ajv-i18n/localize'
8
8
  import simpleGit from 'simple-git'
9
9
  import { nodeCacheDir } from '../../utils/nodeCacheDir'
10
+ import { performance, PerformanceObserver } from 'perf_hooks'
11
+ import { validate } from './util/validate'
10
12
 
11
13
  export interface DeployEvents {
12
14
  process(title: string): void
13
15
  }
14
16
 
15
- /**
16
- * 使用 ajv 校验数据
17
- * @param schema
18
- * @param data
19
- */
20
- function ajvValidate(schema: object, data: object): [isValid: boolean, errorText: string] {
21
- const ajv = new Ajv({ allErrors: true, messages: false })
22
- const res = ajv.validate(schema, data)
23
- if (!res) {
24
- localize.zh(ajv.errors)
25
- }
26
- return [res, ajv.errorsText()]
27
- }
28
-
29
17
  /**
30
18
  * 部署服务接口
31
19
  */
@@ -44,12 +32,13 @@ export enum DeployTypeEnum {
44
32
 
45
33
  export interface BaseDeployOptions {
46
34
  cwd: string
35
+ debug: boolean
47
36
  type: DeployTypeEnum
48
37
  }
49
38
 
50
- export type SftpDeployOptions = Omit<BaseDeployOptions, 'type'> & {
39
+ export interface SftpDeployOptions extends Omit<BaseDeployOptions, 'type'> {
40
+ dist: string
51
41
  dest: string
52
- remote: string
53
42
  sshConfig: ConnectOptions
54
43
  }
55
44
 
@@ -67,38 +56,57 @@ export class SftpDeployService implements IDeployService {
67
56
  ...this.options.sshConfig,
68
57
  privateKey,
69
58
  })
70
- await client.mkdir(this.options.remote, true)
71
- 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)
72
61
  await client.end()
73
62
  })
74
63
  }
75
64
 
76
65
  validate(): [isValidate: boolean, errorText: string] {
77
- return ajvValidate(
66
+ return validate<SftpDeployOptions>(
78
67
  {
79
68
  type: 'object',
80
69
  properties: {
70
+ debug: { type: 'boolean' },
81
71
  cwd: { type: 'string' },
72
+ dist: { type: 'string' },
82
73
  dest: { type: 'string' },
83
- remote: { type: 'string' },
84
74
  sshConfig: {
85
75
  type: 'object',
86
76
  properties: {
87
77
  host: { type: 'string' },
88
78
  username: { type: 'string' },
89
79
  },
90
- },
80
+ } as any,
91
81
  },
92
- required: ['cwd', 'dest', 'remote', 'sshConfig'],
82
+ required: ['cwd', 'dist', 'dest', 'sshConfig', 'debug'],
93
83
  },
94
84
  this.options,
95
85
  )
96
86
  }
97
87
  }
98
88
 
99
- export type GhPagesDeployOptions = Omit<BaseDeployOptions, 'type'> & {
100
- dest: string
101
- 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
102
110
  }
103
111
 
104
112
  /**
@@ -108,48 +116,97 @@ export class GhPagesDeployService implements IDeployService {
108
116
  constructor(private readonly options: GhPagesDeployOptions) {}
109
117
 
110
118
  deploy(): EventExtPromise<void, DeployEvents> {
111
- const defaultRemote = 'origin'
112
- const defaultBranch = 'gh-pages'
119
+ const defaultRemote = this.options.remote ?? 'origin'
120
+ const defaultBranch = this.options.branch ?? 'gh-pages'
113
121
  return PromiseUtil.wrapOnEvent(async (events: DeployEvents) => {
114
- events.process('开始推送')
122
+ const obs = new PerformanceObserver((perfObserverList) => {
123
+ if (this.options.debug) {
124
+ console.log(perfObserverList.getEntries())
125
+ }
126
+ })
127
+ obs.observe({ type: 'mark' })
128
+
129
+ function mark(title: string) {
130
+ performance.mark(title)
131
+ events.process(title)
132
+ }
133
+ mark('开始推送')
115
134
  const git = simpleGit(this.options.cwd)
116
- events.process('获取当前项目的远端配置')
117
- const originRemote = (await git.getRemotes(true)).find((item) => item.name === defaultRemote)
118
- if (!originRemote) {
119
- 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
120
142
  }
121
143
  const ghPagesRoot = path.resolve(nodeCacheDir('liuli-cli'), 'gh-pages')
122
- const originRepoName = originRemote.refs.fetch.replace(new RegExp('[/:]', 'g'), '_')
144
+ const originRepoName = this.options.repo.replace(new RegExp('[/:]', 'g'), '_')
123
145
  const localRepoPath = path.resolve(ghPagesRoot, originRepoName)
146
+ let forcePush = false
124
147
  if (!(await pathExists(localRepoPath))) {
125
- events.process('克隆项目')
126
- await git.clone(originRemote.refs.fetch, localRepoPath, { '--branch': defaultBranch })
148
+ mark('克隆项目')
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
+ }
127
168
  } else {
169
+ await git.cwd(localRepoPath)
170
+ mark('更新项目')
128
171
  await git.pull()
129
172
  }
130
- events.process('复制文件')
131
- const remoteDestPath = path.join(localRepoPath, this.options.remote)
173
+ mark('复制文件')
174
+ const remoteDestPath = path.join(localRepoPath, this.options.dest ?? './')
132
175
  await mkdirp(remoteDestPath)
133
- await copy(path.resolve(this.options.cwd, this.options.dest), remoteDestPath)
134
- events.process('推送到远端')
176
+ await copy(path.resolve(this.options.cwd, this.options.dist), remoteDestPath)
177
+ mark('提交所有文件')
135
178
  await git.cwd(localRepoPath)
136
179
  await git.add('-A')
137
- await git.commit('Updates gh-pages by liuli-cli')
138
- await git.push(defaultRemote, defaultBranch)
139
- events.process('完成推送')
180
+ if ((await git.status()).files.length === 0) {
181
+ mark('没有任何提交,跳过提交')
182
+ } else {
183
+ await git.commit('Updates gh-pages by liuli-cli')
184
+ }
185
+ mark('推送到远端')
186
+ if ((await git.status()).ahead > 0 || forcePush) {
187
+ await git.push(defaultRemote, defaultBranch)
188
+ mark('完成推送')
189
+ } else {
190
+ mark('没有更新,跳过推送')
191
+ }
192
+ obs.disconnect()
140
193
  })
141
194
  }
142
195
 
143
196
  validate(): [isValid: boolean, errorText: string] {
144
- return ajvValidate(
197
+ return validate<GhPagesDeployOptions>(
145
198
  {
146
199
  type: 'object',
147
200
  properties: {
201
+ debug: { type: 'boolean' },
148
202
  cwd: { type: 'string' },
149
- dest: { type: 'string' },
150
- 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 },
151
208
  },
152
- required: ['cwd', 'dest', 'remote'],
209
+ required: ['debug', 'cwd', 'dist'],
153
210
  },
154
211
  this.options,
155
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
  })
@@ -1,6 +1,7 @@
1
1
  import simpleGit, { SimpleGit } from 'simple-git'
2
2
  import * as path from 'path'
3
- import { mkdirp, remove } from 'fs-extra'
3
+ import { mkdirp, pathExists, remove } from 'fs-extra'
4
+ import * as os from 'os'
4
5
 
5
6
  describe('测试 simple-git', () => {
6
7
  async function getOriginRemote() {
@@ -26,4 +27,28 @@ describe('测试 simple-git', () => {
26
27
  const originRepoName = originRemote.refs.fetch.replace(new RegExp('[/:]', 'g'), '_')
27
28
  await git.clone(originRemote.refs.fetch, path.resolve(tempPath, originRepoName), { '--branch': 'gh-pages' })
28
29
  }, 100_000)
30
+ it('测试 git status', async () => {
31
+ await git.add('-A')
32
+ const status = await git.status()
33
+ console.log('status: ', status.files)
34
+ })
35
+ })
36
+
37
+ describe.skip('测试真实场景', () => {
38
+ const gitPath = path.resolve(
39
+ os.homedir(),
40
+ './AppData/Local/liuli-cli/Cache/gh-pages/https___github.com_rxliuli_webos.git',
41
+ )
42
+ const git = simpleGit(gitPath)
43
+ it('测试获取提交状态', async () => {
44
+ const res = await git.status()
45
+ console.log(res)
46
+ })
47
+ it('测试获取更新', async () => {
48
+ const res = await git.pull()
49
+ console.log(res)
50
+ }, 100_000)
51
+ it('测试 git add -A', async () => {
52
+ await git.add('-A')
53
+ })
29
54
  })
@@ -4,4 +4,5 @@ import * as path from 'path'
4
4
 
5
5
  export const deployCommand = new Command('deploy')
6
6
  .description('部署项目到远端')
7
- .action(() => deploy({ cwd: path.resolve() }))
7
+ .option('--debug', '是否开启调试模式')
8
+ .action((options: { debug?: boolean }) => deploy({ cwd: path.resolve(), debug: !!options.debug }))
@@ -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
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "temp",
3
+ "sync": [
4
+ "jest"
5
+ ],
6
+ "jest": {
7
+ "preset": "ts-jest",
8
+ "testMatch": [
9
+ "<rootDir>/src/**/__tests__/*.test.ts"
10
+ ]
11
+ },
12
+ "devDependencies": {
13
+ "jest": "^27.4.3",
14
+ "ts-jest": "^27.0.7"
15
+ }
16
+ }