@liuli-util/cli 3.10.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -3
  3. package/dist/PathUtil.d.ts +6 -6
  4. package/dist/bin.d.ts +1 -1
  5. package/dist/bin.js +102 -86
  6. package/dist/bin.js.map +3 -3
  7. package/dist/commands/esbuild/ESBuildProgram.d.ts +70 -10
  8. package/dist/commands/esbuild/ESBuildProgram.d.ts.map +1 -1
  9. package/dist/commands/esbuild/index.d.ts +2 -2
  10. package/dist/commands/esbuild/index.d.ts.map +1 -1
  11. package/dist/commands/esbuild/util/debounce.d.ts +15 -0
  12. package/dist/commands/esbuild/util/debounce.d.ts.map +1 -0
  13. package/dist/commands/esbuild/util/esbuildPlugins.d.ts +16 -0
  14. package/dist/commands/esbuild/util/esbuildPlugins.d.ts.map +1 -0
  15. package/dist/commands/generate/GenerateProgram.d.ts +17 -17
  16. package/dist/commands/generate/GenerateProgram.d.ts.map +1 -1
  17. package/dist/commands/generate/index.d.ts +2 -2
  18. package/dist/commands/sync/SyncProgram.d.ts +14 -15
  19. package/dist/commands/sync/SyncProgram.d.ts.map +1 -1
  20. package/dist/commands/sync/index.d.ts +2 -2
  21. package/dist/commands/sync/when.d.ts +20 -20
  22. package/dist/index.d.ts +3 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.esm.js +5 -0
  25. package/dist/index.esm.js.map +3 -3
  26. package/dist/index.js +5 -5
  27. package/dist/index.js.map +3 -3
  28. package/dist/utils/AsyncArray.d.ts +20 -20
  29. package/dist/utils/appendScript.d.ts +6 -6
  30. package/dist/utils/arrayToMap.d.ts +2 -2
  31. package/dist/utils/findParent.d.ts +7 -7
  32. package/dist/utils/index.d.ts +4 -4
  33. package/package.json +19 -7
  34. package/src/@types/global.d.ts +8 -0
  35. package/src/bin.ts +2 -2
  36. package/src/commands/esbuild/ESBuildProgram.ts +263 -63
  37. package/src/commands/esbuild/__tests__/ESBuildProgram.test.ts +125 -0
  38. package/src/commands/esbuild/__tests__/spinnies.test.ts +11 -0
  39. package/src/commands/esbuild/index.ts +14 -6
  40. package/src/commands/esbuild/util/debounce.ts +29 -0
  41. package/src/commands/esbuild/util/esbuildPlugins.ts +82 -0
  42. package/src/commands/generate/GenerateProgram.ts +7 -5
  43. package/src/commands/sync/SyncProgram.ts +6 -12
  44. package/src/commands/sync/__tests__/SyncProgram.test.ts +2 -2
  45. package/src/index.ts +3 -1
  46. package/src/utils/__tests__/appendScript.test.ts +1 -1
  47. package/templates/cli/package.json +1 -1
  48. package/templates/lib/package.json +1 -1
  49. package/dist/commands/generate/__tests__/GenerateProgram.test.d.ts +0 -2
  50. package/dist/commands/generate/__tests__/GenerateProgram.test.d.ts.map +0 -1
  51. package/dist/commands/sync/__tests__/SyncProgram.test.d.ts +0 -2
  52. package/dist/commands/sync/__tests__/SyncProgram.test.d.ts.map +0 -1
  53. package/dist/commands/sync/__tests__/when.test.d.ts +0 -2
  54. package/dist/commands/sync/__tests__/when.test.d.ts.map +0 -1
  55. package/dist/tsconfig.tsbuildinfo +0 -1
  56. package/dist/utils/__tests__/appendScript.test.d.ts +0 -2
  57. package/dist/utils/__tests__/appendScript.test.d.ts.map +0 -1
  58. package/dist/utils/__tests__/arrayToMap.test.d.ts +0 -2
  59. package/dist/utils/__tests__/arrayToMap.test.d.ts.map +0 -1
  60. package/dist/utils/__tests__/findParent.test.d.ts +0 -2
  61. package/dist/utils/__tests__/findParent.test.d.ts.map +0 -1
  62. package/dist/utils/execPromise.d.ts +0 -4
  63. package/dist/utils/execPromise.d.ts.map +0 -1
  64. package/src/utils/execPromise.ts +0 -16
@@ -0,0 +1,125 @@
1
+ import { ESBuildProgram } from '../ESBuildProgram'
2
+ import * as path from 'path'
3
+ import { mkdirp, pathExists, remove, writeJson } from 'fs-extra'
4
+ import { PackageJson } from 'type-fest'
5
+ import { build, Platform, Plugin } from 'esbuild'
6
+ import { nativeNodeModules, nodeExternals } from '../util/esbuildPlugins'
7
+
8
+ describe('测试 ESBuildProgram', () => {
9
+ describe('测试 getPlatform', () => {
10
+ const base: string = path.resolve(__dirname, '.temp/getPlatform')
11
+ beforeEach(async () => {
12
+ await remove(base)
13
+ await mkdirp(base)
14
+ })
15
+ it('测试 node 类型', async () => {
16
+ await writeJson(path.resolve(base, 'package.json'), {
17
+ devDependencies: {
18
+ '@types/node': '16',
19
+ },
20
+ } as PackageJson)
21
+ expect(await ESBuildProgram.getPlatform(base)).toBe('node' as Platform)
22
+ })
23
+ it('测试浏览器类型', async () => {
24
+ await writeJson(path.resolve(base, 'tsconfig.json'), {
25
+ compilerOptions: {
26
+ lib: ['DOM'],
27
+ },
28
+ })
29
+ expect(await ESBuildProgram.getPlatform(base)).toBe('browser' as Platform)
30
+ })
31
+ it('测试通用类型', async () => {
32
+ expect(await ESBuildProgram.getPlatform(base)).toBe('neutral' as Platform)
33
+ })
34
+ })
35
+ describe('测试 getDeps', () => {
36
+ const base: string = path.resolve(__dirname, '.temp/getDeps')
37
+ beforeEach(async () => {
38
+ await remove(base)
39
+ await mkdirp(base)
40
+ })
41
+ it('基本示例', async () => {
42
+ await writeJson(path.resolve(base, 'package.json'), {
43
+ devDependencies: {
44
+ '@types/node': '16',
45
+ },
46
+ dependencies: {
47
+ ora: '^6',
48
+ },
49
+ peerDependencies: {
50
+ typescript: '^4',
51
+ },
52
+ } as PackageJson)
53
+ const res = (await ESBuildProgram.getDeps(base)).sort()
54
+ console.log(res)
55
+ expect(res).toEqual(['@types/node', 'ora', 'typescript'].sort())
56
+ })
57
+ })
58
+ describe('测试构建相关', () => {
59
+ const base: string = path.resolve()
60
+ const program = new ESBuildProgram({
61
+ base,
62
+ isWatch: false,
63
+ })
64
+ let deps: string[]
65
+ let platform: Platform
66
+ let plugins: Plugin[]
67
+ beforeEach(async () => {
68
+ await remove(path.resolve(base, 'dist'))
69
+ deps = await ESBuildProgram.getDeps(base)
70
+ platform = await ESBuildProgram.getPlatform(base)
71
+ plugins = ESBuildProgram.getPlugins(platform)
72
+ })
73
+ it('测试 genDTS', async () => {
74
+ await program.genDTS()
75
+ expect(
76
+ await pathExists(path.resolve(base, 'dist/index.d.ts')),
77
+ ).toBeTruthy()
78
+ })
79
+ it('测试 getBuildCjsOption', async () => {
80
+ await build(
81
+ program.getBuildCjsOption({
82
+ deps: deps,
83
+ platform: platform,
84
+ }),
85
+ )
86
+ expect(await pathExists(path.resolve(base, 'dist/index.js'))).toBeTruthy()
87
+ })
88
+ it('测试 getBuildESMOption', async () => {
89
+ const option = program.getBuildESMOption({
90
+ deps: deps,
91
+ platform: platform,
92
+ })
93
+ console.log('option: ', deps, option)
94
+ await build(option)
95
+ expect(
96
+ await pathExists(path.resolve(base, 'dist/index.esm.js')),
97
+ ).toBeTruthy()
98
+ })
99
+ it('测试 getBuildCliOption', async () => {
100
+ await build(
101
+ program.getBuildCliOption({
102
+ deps: deps,
103
+ platform: platform,
104
+ }),
105
+ )
106
+ expect(await pathExists(path.resolve(base, 'dist/bin.js'))).toBeTruthy()
107
+ })
108
+ })
109
+ })
110
+ it('测试 esbuild', async () => {
111
+ await build({
112
+ outfile: './dist/bin.js',
113
+ format: 'cjs',
114
+ sourcemap: true,
115
+ entryPoints: ['./src/bin.ts'],
116
+ bundle: true,
117
+ external: [
118
+ ...ESBuildProgram.globalExternal,
119
+ // ...(await ESBuildProgram.getDeps(path.resolve())),
120
+ ],
121
+ platform: 'node',
122
+ plugins: [nativeNodeModules(), nodeExternals()],
123
+ treeShaking: true,
124
+ })
125
+ })
@@ -0,0 +1,11 @@
1
+ import Spinnies from 'spinnies'
2
+
3
+ it('测试 spinnies', async () => {
4
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
5
+ const spinnies = new Spinnies()
6
+ spinnies.add('a', { text: '开始任务 a' })
7
+ spinnies.add('b', { text: '开始任务 b' })
8
+ await wait(3000)
9
+ spinnies.succeed('a', { text: '成功运行任务 a' })
10
+ spinnies.fail('b', { text: '成功运行任务 b' })
11
+ })
@@ -2,15 +2,19 @@ import { Command } from 'commander'
2
2
  import { ESBuildProgram } from './ESBuildProgram'
3
3
  import path from 'path'
4
4
 
5
- const buildProgram = new ESBuildProgram(path.resolve())
6
- export const buildCommand = new Command('build')
5
+ export const esbuildCommand = new Command('build')
7
6
  .addCommand(
8
- new Command('pkg')
7
+ new Command('lib')
9
8
  .description('使用 esbulid 将 ts lib 打包到 dist 目录,格式为 esm/cjs')
10
9
  .option('-w --watch', '监视模式')
11
10
  .action(async (option: { watch?: boolean }) => {
12
- await buildProgram.buildPkg(!!option.watch)
13
- }),
11
+ const buildProgram = new ESBuildProgram({
12
+ base: path.resolve(),
13
+ isWatch: !!option.watch,
14
+ })
15
+ await buildProgram.buildLib()
16
+ })
17
+ .alias('pkg'),
14
18
  )
15
19
  .addCommand(
16
20
  new Command('cli')
@@ -19,6 +23,10 @@ export const buildCommand = new Command('build')
19
23
  )
20
24
  .option('-w, --watch', '监视模式')
21
25
  .action(async (option: { watch?: boolean }) => {
22
- await buildProgram.buildCli(!!option.watch)
26
+ const buildProgram = new ESBuildProgram({
27
+ base: path.resolve(),
28
+ isWatch: !!option.watch,
29
+ })
30
+ await buildProgram.buildCli()
23
31
  }),
24
32
  )
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 函数去抖
3
+ * 去抖 (debounce) 去抖就是对于一定时间段的连续的函数调用,只让其执行一次
4
+ * 注: 包装后的函数如果两次操作间隔小于 delay 则不会被执行, 如果一直在操作就会一直不执行, 直到操作停止的时间大于 delay 最小间隔时间才会执行一次, 不管任何时间调用都需要停止操作等待最小延迟时间
5
+ * 应用场景主要在那些连续的操作, 例如页面滚动监听, 包装后的函数只会执行最后一次
6
+ * 注: 该函数第一次调用一定不会执行,第一次一定拿不到缓存值,后面的连续调用都会拿到上一次的缓存值。如果需要在第一次调用获取到的缓存值,则需要传入第三个参数 {@param init},默认为 {@code undefined} 的可选参数
7
+ * 注: 返回函数结果的高阶函数需要使用 {@see Proxy} 实现,以避免原函数原型链上的信息丢失
8
+ *
9
+ * @param fn 真正需要执行的操作
10
+ * @param delay 最小延迟时间,单位为 ms
11
+ * @param init 初始的缓存值,不填默认为 {@see undefined}
12
+ * @return 包装后有去抖功能的函数。该函数是异步的,与需要包装的函数 {@param fn} 是否异步没有太大关联
13
+ */
14
+ export function debounce<
15
+ T extends (...args: any[]) => any,
16
+ R extends (...args: Parameters<T>) => Promise<ReturnType<T>>,
17
+ >(fn: T, delay: number, init: any = null): R {
18
+ let flag: number
19
+ let result = init
20
+ return async function (...args) {
21
+ return new Promise((resolve) => {
22
+ if (flag) {
23
+ clearTimeout(flag as any)
24
+ }
25
+ flag = setTimeout(() => resolve((result = fn(...args))), delay) as any
26
+ setTimeout(() => resolve(result), delay)
27
+ })
28
+ } as R
29
+ }
@@ -0,0 +1,82 @@
1
+ import { Plugin } from 'esbuild'
2
+
3
+ /**
4
+ * 处理 nodejs 原生模块
5
+ * @link https://github.com/evanw/esbuild/issues/1051#issuecomment-806325487
6
+ */
7
+ export function nativeNodeModules(): Plugin {
8
+ return {
9
+ name: 'native-node-modules',
10
+ setup(build) {
11
+ // If a ".node" file is imported within a module in the "file" namespace, resolve
12
+ // it to an absolute path and put it into the "node-file" virtual namespace.
13
+ build.onResolve({ filter: /\.node$/, namespace: 'file' }, (args) => ({
14
+ path: require.resolve(args.path, { paths: [args.resolveDir] }),
15
+ namespace: 'node-file',
16
+ }))
17
+
18
+ // Files in the "node-file" virtual namespace call "require()" on the
19
+ // path from esbuild of the ".node" file in the output directory.
20
+ build.onLoad({ filter: /.*/, namespace: 'node-file' }, (args) => ({
21
+ contents: `
22
+ import path from ${JSON.stringify(args.path)}
23
+ try { module.exports = require(path) }
24
+ catch {}
25
+ `,
26
+ }))
27
+
28
+ // If a ".node" file is imported within a module in the "node-file" namespace, put
29
+ // it in the "file" namespace where esbuild's default loading behavior will handle
30
+ // it. It is already an absolute path since we resolved it to one above.
31
+ build.onResolve(
32
+ { filter: /\.node$/, namespace: 'node-file' },
33
+ (args) => ({
34
+ path: args.path,
35
+ namespace: 'file',
36
+ }),
37
+ )
38
+
39
+ // Tell esbuild's default loading behavior to use the "file" loader for
40
+ // these ".node" files.
41
+ const opts = build.initialOptions
42
+ opts.loader = opts.loader || {}
43
+ opts.loader['.node'] = 'file'
44
+ },
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 排除和替换 node 内置模块
50
+ */
51
+ export function nodeExternals(): Plugin {
52
+ return {
53
+ name: 'esbuild-plugin-node-externals',
54
+ setup(build) {
55
+ build.onResolve({ filter: /(^node:)/ }, (args) => ({
56
+ path: args.path.slice(5),
57
+ external: true,
58
+ }))
59
+ },
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 自动排除所有依赖项
65
+ * golang 不支持 js 的一些语法,参考 https://github.com/evanw/esbuild/issues/1634
66
+ */
67
+ export function autoExternal(): Plugin {
68
+ return {
69
+ name: 'esbuild-plugin-auto-external',
70
+ setup(build) {
71
+ build.onResolve({ filter: /.*/ }, (args) => {
72
+ if (/^\.{1,2}\//.test(args.path)) {
73
+ return
74
+ }
75
+ return {
76
+ path: args.path,
77
+ external: true,
78
+ }
79
+ })
80
+ },
81
+ }
82
+ }
@@ -9,7 +9,7 @@ import {
9
9
  writeFile,
10
10
  writeJSON,
11
11
  } from 'fs-extra'
12
- import inquirer from 'inquirer'
12
+ import { prompt } from 'enquirer'
13
13
  import { SyncProgram } from '../sync/SyncProgram'
14
14
  import { PathUtil } from '../../PathUtil'
15
15
 
@@ -30,7 +30,7 @@ export class GenerateProgram {
30
30
  */
31
31
  async generate(config: GenerateConfig): Promise<void> {
32
32
  if (!config.dest) {
33
- const { dest } = await inquirer.prompt({
33
+ const { dest } = await prompt<{ dest: string }>({
34
34
  name: 'dest',
35
35
  type: 'input',
36
36
  message: '请输入项目名',
@@ -41,7 +41,7 @@ export class GenerateProgram {
41
41
  config.dest = path.resolve(dest)
42
42
  }
43
43
  if (!config.template) {
44
- const { template } = await inquirer.prompt({
44
+ const { template } = await prompt<{ template: TemplateTypeEnum }>({
45
45
  name: 'template',
46
46
  type: 'list',
47
47
  message: '请选择模板',
@@ -69,10 +69,12 @@ export class GenerateProgram {
69
69
  (await pathExists(destFile)) &&
70
70
  (await readdir(destFile)).some((file) => pathExists(file))
71
71
  ) {
72
- const { override } = await inquirer.prompt({
72
+ const { override } = await prompt<{
73
+ override: boolean
74
+ }>({
73
75
  name: 'override',
74
76
  type: 'confirm',
75
- default: true,
77
+ initial: true,
76
78
  message: '目标位置不是一个空目录,确认要覆盖么?',
77
79
  })
78
80
  if (!override) {
@@ -1,24 +1,16 @@
1
1
  import { readFile, readJson, writeFile, writeJson } from 'fs-extra'
2
2
  import path from 'path'
3
- import { execSync } from 'child_process'
4
- import { merge } from 'lodash'
3
+ import { merge } from 'lodash-es'
5
4
  import { PackageJson } from 'type-fest'
6
5
  import prettier from '@liuli-util/prettier-standard-config/package.json'
7
6
  import eslintTs from '@liuli-util/eslint-config-ts/package.json'
8
7
  import eslintReactTs from '@liuli-util/eslint-config-react-ts/package.json'
9
8
  import commitlint from '@liuli-util/commitlint-standard-config/package.json'
10
- import { prompt } from 'inquirer'
9
+ import { prompt } from 'enquirer'
11
10
  import { isIncludeDep, isNpmPackage, isYarnRoot, isYarnSubModule } from './when'
12
11
  import { appendScript, arrayToMap, AsyncArray } from '../../utils'
13
12
  import { PathUtil } from '../../PathUtil'
14
13
 
15
- export function installDeps(base: string, deps: string[]): void {
16
- execSync('yarn add -D -W ' + deps.join(' '), {
17
- stdio: 'inherit',
18
- cwd: base,
19
- })
20
- }
21
-
22
14
  export async function mergeJson(base: string, json: object): Promise<void> {
23
15
  const pkgJsonFilePath = path.resolve(base, './package.json')
24
16
  await writeJson(
@@ -240,8 +232,10 @@ export class SyncProgram {
240
232
  }),
241
233
  (item) => item.type,
242
234
  )
243
- const res = await prompt({
244
- type: 'checkbox',
235
+ const res = await prompt<{
236
+ sync: string[]
237
+ }>({
238
+ type: 'multiselect',
245
239
  message: '请选择需要同步的配置项',
246
240
  name: 'sync',
247
241
  choices: [...configMap.keys()],
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readJson, remove, writeJson } from 'fs-extra'
2
2
  import path from 'path'
3
3
  import { SyncConfigType, SyncProgram } from '../SyncProgram'
4
- import { merge } from 'lodash'
4
+ import { merge } from 'lodash-es'
5
5
  import { PackageJson } from 'type-fest'
6
6
 
7
7
  describe('测试 SyncProgram', () => {
@@ -44,7 +44,7 @@ describe('测试 SyncProgram', () => {
44
44
  }, 100_000)
45
45
  })
46
46
 
47
- it('测试 lodash.merge', () => {
47
+ it('测试 lodash-es.merge', () => {
48
48
  const res = merge({ arr: ['a'] }, { arr: ['b'] })
49
49
  expect(res).toEqual({ arr: ['b'] })
50
50
  })
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
- export {}
1
+ export * from './commands/esbuild/ESBuildProgram'
2
+ export * from './commands/generate/GenerateProgram'
3
+ export * from './commands/sync/SyncProgram'
@@ -1,7 +1,7 @@
1
1
  import { appendScript } from '../appendScript'
2
2
 
3
3
  describe('测试 appendScript', () => {
4
- const newScript = 'pinefield sync'
4
+ const newScript = 'liuli-cli sync'
5
5
  it('脚本不存在', () => {
6
6
  expect(appendScript(undefined, newScript)).toBe(newScript)
7
7
  })
@@ -26,7 +26,7 @@
26
26
  "inquirer": "^8.1.5"
27
27
  },
28
28
  "devDependencies": {
29
- "@liuli-util/cli": "^3.10.1",
29
+ "@liuli-util/cli": "^3.11.0",
30
30
  "@types/fs-extra": "^9.0.13",
31
31
  "@types/inquirer": "^8.1.2",
32
32
  "@types/jest": "^27.0.2",
@@ -18,7 +18,7 @@
18
18
  "preset": "ts-jest"
19
19
  },
20
20
  "devDependencies": {
21
- "@liuli-util/cli": "^3.10.1",
21
+ "@liuli-util/cli": "^3.11.0",
22
22
  "@types/jest": "^27.0.2",
23
23
  "esno": "^0.9.1",
24
24
  "gh-pages": "^3.2.3",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=GenerateProgram.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"GenerateProgram.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/generate/__tests__/GenerateProgram.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=SyncProgram.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SyncProgram.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/sync/__tests__/SyncProgram.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=when.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"when.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/sync/__tests__/when.test.ts"],"names":[],"mappings":""}