@liuli-util/cli 3.9.0 → 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 (82) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +3 -3
  3. package/_gitignore +33 -0
  4. package/dist/PathUtil.d.ts +7 -0
  5. package/dist/PathUtil.d.ts.map +1 -0
  6. package/dist/bin.d.ts +1 -1
  7. package/dist/bin.js +116 -1
  8. package/dist/bin.js.map +7 -1
  9. package/dist/commands/esbuild/ESBuildProgram.d.ts +71 -0
  10. package/dist/commands/esbuild/ESBuildProgram.d.ts.map +1 -0
  11. package/dist/commands/esbuild/index.d.ts +3 -0
  12. package/dist/commands/esbuild/index.d.ts.map +1 -0
  13. package/dist/commands/esbuild/util/debounce.d.ts +15 -0
  14. package/dist/commands/esbuild/util/debounce.d.ts.map +1 -0
  15. package/dist/commands/esbuild/util/esbuildPlugins.d.ts +16 -0
  16. package/dist/commands/esbuild/util/esbuildPlugins.d.ts.map +1 -0
  17. package/dist/commands/generate/GenerateProgram.d.ts +18 -0
  18. package/dist/commands/generate/GenerateProgram.d.ts.map +1 -0
  19. package/dist/commands/generate/index.d.ts +3 -0
  20. package/dist/commands/generate/index.d.ts.map +1 -0
  21. package/dist/commands/sync/SyncProgram.d.ts +15 -0
  22. package/dist/commands/sync/SyncProgram.d.ts.map +1 -0
  23. package/dist/commands/sync/index.d.ts +3 -0
  24. package/dist/commands/sync/index.d.ts.map +1 -0
  25. package/dist/commands/sync/when.d.ts +21 -0
  26. package/dist/commands/sync/when.d.ts.map +1 -0
  27. package/dist/index.d.ts +3 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.esm.js +5 -1
  30. package/dist/index.esm.js.map +7 -1
  31. package/dist/index.js +5 -1
  32. package/dist/index.js.map +7 -1
  33. package/dist/utils/AsyncArray.d.ts +21 -0
  34. package/dist/utils/AsyncArray.d.ts.map +1 -0
  35. package/dist/utils/appendScript.d.ts +7 -0
  36. package/dist/utils/appendScript.d.ts.map +1 -0
  37. package/dist/utils/arrayToMap.d.ts +3 -0
  38. package/dist/utils/arrayToMap.d.ts.map +1 -0
  39. package/dist/utils/findParent.d.ts +8 -0
  40. package/dist/utils/findParent.d.ts.map +1 -0
  41. package/dist/utils/index.d.ts +5 -0
  42. package/dist/utils/index.d.ts.map +1 -0
  43. package/package.json +34 -22
  44. package/src/@types/global.d.ts +8 -0
  45. package/src/PathUtil.ts +8 -0
  46. package/src/bin.ts +5 -5
  47. package/src/commands/esbuild/ESBuildProgram.ts +288 -0
  48. package/src/commands/esbuild/__tests__/ESBuildProgram.test.ts +125 -0
  49. package/src/commands/esbuild/__tests__/spinnies.test.ts +11 -0
  50. package/src/commands/esbuild/index.ts +32 -0
  51. package/src/commands/esbuild/util/debounce.ts +29 -0
  52. package/src/commands/esbuild/util/esbuildPlugins.ts +82 -0
  53. package/src/commands/generate/GenerateProgram.ts +115 -0
  54. package/src/commands/generate/__tests__/GenerateProgram.test.ts +51 -0
  55. package/src/commands/generate/index.ts +26 -0
  56. package/src/commands/sync/SyncProgram.ts +247 -0
  57. package/src/commands/sync/__tests__/SyncProgram.test.ts +50 -0
  58. package/src/commands/sync/__tests__/when.test.ts +46 -0
  59. package/src/commands/sync/index.ts +15 -0
  60. package/src/commands/sync/when.ts +67 -0
  61. package/src/index.ts +3 -1
  62. package/src/utils/AsyncArray.ts +144 -0
  63. package/src/utils/__tests__/appendScript.test.ts +15 -0
  64. package/src/utils/__tests__/arrayToMap.test.ts +37 -0
  65. package/src/utils/__tests__/findParent.test.ts +15 -0
  66. package/src/utils/appendScript.ts +17 -0
  67. package/src/utils/arrayToMap.ts +27 -0
  68. package/src/utils/findParent.ts +36 -0
  69. package/src/utils/index.ts +4 -0
  70. package/templates/cli/CHANGELOG.md +1 -0
  71. package/templates/cli/README.md +1 -0
  72. package/templates/cli/package.json +44 -0
  73. package/templates/cli/src/bin.ts +13 -0
  74. package/templates/cli/src/index.ts +1 -0
  75. package/templates/cli/tsconfig.json +28 -0
  76. package/templates/lib/CHANGELOG.md +1 -0
  77. package/templates/lib/README.md +1 -0
  78. package/templates/lib/package.json +32 -0
  79. package/templates/lib/src/__tests__/hello.test.ts +5 -0
  80. package/templates/lib/src/index.ts +3 -0
  81. package/templates/lib/tsconfig.json +28 -0
  82. package/tsconfig.json +34 -28
@@ -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
+ }
@@ -0,0 +1,115 @@
1
+ import path from 'path'
2
+ import {
3
+ copy,
4
+ pathExists,
5
+ readdir,
6
+ readFile,
7
+ readJSON,
8
+ remove,
9
+ writeFile,
10
+ writeJSON,
11
+ } from 'fs-extra'
12
+ import { prompt } from 'enquirer'
13
+ import { SyncProgram } from '../sync/SyncProgram'
14
+ import { PathUtil } from '../../PathUtil'
15
+
16
+ export enum TemplateTypeEnum {
17
+ Cli = 'cli',
18
+ Lib = 'lib',
19
+ }
20
+
21
+ export interface GenerateConfig {
22
+ template: TemplateTypeEnum
23
+ dest: string
24
+ initSync?: boolean
25
+ }
26
+
27
+ export class GenerateProgram {
28
+ /**
29
+ * 生成项目
30
+ */
31
+ async generate(config: GenerateConfig): Promise<void> {
32
+ if (!config.dest) {
33
+ const { dest } = await prompt<{ dest: string }>({
34
+ name: 'dest',
35
+ type: 'input',
36
+ message: '请输入项目名',
37
+ validate(input: string): boolean {
38
+ return input.trim() !== ''
39
+ },
40
+ })
41
+ config.dest = path.resolve(dest)
42
+ }
43
+ if (!config.template) {
44
+ const { template } = await prompt<{ template: TemplateTypeEnum }>({
45
+ name: 'template',
46
+ type: 'list',
47
+ message: '请选择模板',
48
+ choices: [
49
+ TemplateTypeEnum.Lib,
50
+ TemplateTypeEnum.Cli,
51
+ ] as TemplateTypeEnum[],
52
+ })
53
+ config.template = template
54
+ }
55
+
56
+ /*
57
+ 克隆项目
58
+ 修改一些内容
59
+ 共通修改
60
+ - 修改 package.json,删除 private,修改名字
61
+ 模板特定修改
62
+ */
63
+ const srcFile = path.resolve(
64
+ PathUtil.RootPath,
65
+ `templates/${config.template}`,
66
+ )
67
+ const destFile = path.resolve(config.dest)
68
+ if (
69
+ (await pathExists(destFile)) &&
70
+ (await readdir(destFile)).some((file) => pathExists(file))
71
+ ) {
72
+ const { override } = await prompt<{
73
+ override: boolean
74
+ }>({
75
+ name: 'override',
76
+ type: 'confirm',
77
+ initial: true,
78
+ message: '目标位置不是一个空目录,确认要覆盖么?',
79
+ })
80
+ if (!override) {
81
+ return
82
+ }
83
+ }
84
+ await remove(destFile)
85
+ await copy(srcFile, destFile)
86
+ await GenerateProgram.updatePackageJSON(destFile)
87
+ await GenerateProgram.updateReadme(destFile)
88
+ if (config.initSync) {
89
+ const syncProgram = new SyncProgram(path.resolve(config.dest))
90
+ await syncProgram.init()
91
+ await syncProgram.sync()
92
+ }
93
+ }
94
+
95
+ static async updatePackageJSON(destFile: string): Promise<void> {
96
+ const pkgPath = path.resolve(destFile, 'package.json')
97
+ await writeJSON(
98
+ pkgPath,
99
+ {
100
+ ...(await readJSON(pkgPath)),
101
+ name: path.basename(destFile),
102
+ },
103
+ {
104
+ spaces: 2,
105
+ },
106
+ )
107
+ }
108
+
109
+ static async updateReadme(destFile: string): Promise<void> {
110
+ const readmePath = path.resolve(destFile, 'README.md')
111
+ let readmeFile = await readFile(readmePath, 'utf-8')
112
+ readmeFile = readmeFile.replace('template', path.basename(destFile))
113
+ await writeFile(readmePath, readmeFile)
114
+ }
115
+ }
@@ -0,0 +1,51 @@
1
+ import { GenerateProgram, TemplateTypeEnum } from '../GenerateProgram'
2
+ import path from 'path'
3
+ import {
4
+ mkdir,
5
+ pathExists,
6
+ readFile,
7
+ readJson,
8
+ remove,
9
+ writeFile,
10
+ writeJson,
11
+ } from 'fs-extra'
12
+ import { PackageJson } from 'type-fest'
13
+
14
+ describe('测试 InitProgram', () => {
15
+ const initProgram = new GenerateProgram()
16
+ const tempPath = path.resolve(__dirname, 'temp')
17
+ it('生成项目', async () => {
18
+ const dest = path.resolve(tempPath, 'lib-demo')
19
+ await remove(dest)
20
+ await initProgram.generate({
21
+ template: TemplateTypeEnum.Lib,
22
+ dest,
23
+ })
24
+ expect(await pathExists(dest)).toBeTruthy()
25
+ }, 100_000)
26
+ describe('测试一些钩子', () => {
27
+ beforeAll(async () => {
28
+ await remove(tempPath)
29
+ await mkdir(tempPath)
30
+ })
31
+ it('测试修改 package.json', async () => {
32
+ const jsonPath = path.resolve(tempPath, 'package.json')
33
+ await writeJson(jsonPath, {
34
+ name: '@liuli-util/template',
35
+ })
36
+ await GenerateProgram.updatePackageJSON(tempPath)
37
+ expect(
38
+ ((await readJson(jsonPath)) as PackageJson).name?.endsWith('temp'),
39
+ ).toBeTruthy()
40
+ })
41
+ it('测试修改 readme', async () => {
42
+ const readmePath = path.resolve(tempPath, 'README.md')
43
+ await writeFile(readmePath, `# @liuli-util/template`)
44
+ await GenerateProgram.updateReadme(tempPath)
45
+ console.log('readme: ', await readFile(readmePath, 'utf-8'))
46
+ expect(
47
+ (await readFile(readmePath, 'utf-8')).endsWith('temp'),
48
+ ).toBeTruthy()
49
+ })
50
+ })
51
+ })
@@ -0,0 +1,26 @@
1
+ import { Command, Option } from 'commander'
2
+ import {
3
+ GenerateConfig,
4
+ GenerateProgram,
5
+ TemplateTypeEnum,
6
+ } from './GenerateProgram'
7
+
8
+ const generateProgram = new GenerateProgram()
9
+
10
+ const templateOption = new Option('--template [template]', '模板类型').choices([
11
+ TemplateTypeEnum.Lib,
12
+ TemplateTypeEnum.Cli,
13
+ ])
14
+ templateOption.required = true
15
+
16
+ export const generateCommand = new Command()
17
+ .command('generate [dest]')
18
+ .description('生成一些初始项目')
19
+ .addOption(templateOption)
20
+ .action(async (dest, options: Pick<GenerateConfig, 'template'>) => {
21
+ await generateProgram.generate({
22
+ ...options,
23
+ dest: dest,
24
+ initSync: true,
25
+ })
26
+ })
@@ -0,0 +1,247 @@
1
+ import { readFile, readJson, writeFile, writeJson } from 'fs-extra'
2
+ import path from 'path'
3
+ import { merge } from 'lodash-es'
4
+ import { PackageJson } from 'type-fest'
5
+ import prettier from '@liuli-util/prettier-standard-config/package.json'
6
+ import eslintTs from '@liuli-util/eslint-config-ts/package.json'
7
+ import eslintReactTs from '@liuli-util/eslint-config-react-ts/package.json'
8
+ import commitlint from '@liuli-util/commitlint-standard-config/package.json'
9
+ import { prompt } from 'enquirer'
10
+ import { isIncludeDep, isNpmPackage, isYarnRoot, isYarnSubModule } from './when'
11
+ import { appendScript, arrayToMap, AsyncArray } from '../../utils'
12
+ import { PathUtil } from '../../PathUtil'
13
+
14
+ export async function mergeJson(base: string, json: object): Promise<void> {
15
+ const pkgJsonFilePath = path.resolve(base, './package.json')
16
+ await writeJson(
17
+ pkgJsonFilePath,
18
+ merge(await readJson(pkgJsonFilePath), json),
19
+ {
20
+ spaces: 2,
21
+ },
22
+ )
23
+ }
24
+
25
+ export type SyncConfigType =
26
+ | 'prettier'
27
+ | 'commitlint'
28
+ | 'simplehooks'
29
+ | 'workspaces'
30
+ | 'gitignore'
31
+ | 'eslint-ts'
32
+ | 'eslint-vue-ts'
33
+ | 'eslint-react-ts'
34
+ | 'jest'
35
+
36
+ export interface SyncConfig {
37
+ type: SyncConfigType
38
+ handler(): Promise<void>
39
+ when?(): Promise<boolean>
40
+ }
41
+
42
+ export class SyncProgram {
43
+ constructor(private readonly base: string) {}
44
+ private syncConfigs: SyncConfig[] = [
45
+ {
46
+ type: 'workspaces',
47
+ handler: async () => {
48
+ const pkgPath = path.resolve(this.base, './package.json')
49
+ const lernaPath = path.resolve(this.base, 'lerna.json')
50
+ const pkgJson = (await readJson(pkgPath)) as { workspaces: string[] }
51
+ const lernaJson = await readJson(lernaPath)
52
+ lernaJson.packages = pkgJson.workspaces
53
+ await writeJson(lernaPath, lernaJson, {
54
+ spaces: 2,
55
+ })
56
+ },
57
+ when: isYarnRoot,
58
+ },
59
+ {
60
+ type: 'prettier',
61
+ handler: async () => {
62
+ await mergeJson(this.base, {
63
+ prettier: '@liuli-util/prettier-standard-config',
64
+ devDependencies: {
65
+ prettier: '^2.3.2',
66
+ '@liuli-util/prettier-standard-config': `^${prettier.version}`,
67
+ },
68
+ } as PackageJson)
69
+ },
70
+ async when(): Promise<boolean> {
71
+ return (
72
+ (await isNpmPackage()) &&
73
+ ((await isYarnRoot()) || !(await isYarnSubModule()))
74
+ )
75
+ },
76
+ },
77
+ {
78
+ type: 'commitlint',
79
+ handler: async () => {
80
+ await mergeJson(this.base, {
81
+ 'simple-git-hooks': {
82
+ 'commit-msg': 'yarn commitlint --edit $1',
83
+ },
84
+ commitlint: {
85
+ extends: ['@liuli-util/commitlint-standard-config'],
86
+ },
87
+ devDependencies: {
88
+ '@commitlint/cli': '^12.1.4',
89
+ '@liuli-util/commitlint-standard-config': `^${commitlint.version}`,
90
+ },
91
+ } as PackageJson)
92
+ },
93
+ async when(): Promise<boolean> {
94
+ return (
95
+ (await isNpmPackage()) &&
96
+ ((await isYarnRoot()) || !(await isYarnSubModule()))
97
+ )
98
+ },
99
+ },
100
+ {
101
+ type: 'gitignore',
102
+ handler: async () => {
103
+ const gitignorePath = path.resolve(this.base, '.gitignore')
104
+ await writeFile(
105
+ gitignorePath,
106
+ await readFile(
107
+ path.resolve(PathUtil.RootPath, '_gitignore'),
108
+ 'utf-8',
109
+ ),
110
+ )
111
+ },
112
+ },
113
+ {
114
+ type: 'eslint-ts',
115
+ handler: async () => {
116
+ await mergeJson(this.base, {
117
+ eslintConfig: {
118
+ extends: ['@liuli-util/eslint-config-ts'],
119
+ },
120
+ devDependencies: {
121
+ '@liuli-util/eslint-config-ts': `^${eslintTs.version}`,
122
+ },
123
+ } as PackageJson)
124
+ },
125
+ async when(): Promise<boolean> {
126
+ return (
127
+ (await isNpmPackage()) &&
128
+ !(await isIncludeDep(['vue'])) &&
129
+ !(await isIncludeDep(['react']))
130
+ )
131
+ },
132
+ },
133
+ {
134
+ type: 'eslint-react-ts',
135
+ handler: async () => {
136
+ await mergeJson(this.base, {
137
+ eslintConfig: {
138
+ extends: ['@liuli-util/eslint-config-react-ts'],
139
+ },
140
+ devDependencies: {
141
+ '@liuli-util/eslint-config-react-ts': `^${eslintReactTs.version}`,
142
+ },
143
+ } as PackageJson)
144
+ },
145
+ async when(): Promise<boolean> {
146
+ return (await isNpmPackage()) && (await isIncludeDep(['react']))
147
+ },
148
+ },
149
+ {
150
+ type: 'jest',
151
+ handler: async () => {
152
+ await mergeJson(this.base, {
153
+ jest: {
154
+ preset: 'ts-jest',
155
+ testMatch: ['<rootDir>/src/**/__tests__/*.test.ts'],
156
+ },
157
+ })
158
+ },
159
+ },
160
+ //必须放到最后一个,后续根据检测结果添加 hooks
161
+ {
162
+ type: 'simplehooks',
163
+ handler: async () => {
164
+ const json = await readJson(path.resolve(this.base, './package.json'))
165
+ const lintStaged: string[] = []
166
+ if (json.prettier) {
167
+ lintStaged.push('prettier --write')
168
+ }
169
+ if (json.eslintConfig) {
170
+ lintStaged.push('eslint --fix')
171
+ }
172
+ let config = {
173
+ scripts: {
174
+ postinstall: appendScript(
175
+ json?.scripts?.postinstall,
176
+ 'npx simple-git-hooks',
177
+ ),
178
+ },
179
+ 'simple-git-hooks': {
180
+ 'pre-commit': 'yarn lint-staged',
181
+ },
182
+ 'lint-staged': {
183
+ '*.{ts,tsx,js,jsx,css,vue}': [...lintStaged, 'git add'],
184
+ },
185
+ devDependencies: {
186
+ 'simple-git-hooks': '^2.5.1',
187
+ 'lint-staged': '^11.1.1',
188
+ },
189
+ }
190
+ if (json.commitlint) {
191
+ config = merge(config, {
192
+ 'simple-git-hooks': {
193
+ 'commit-msg': 'yarn commitlint --edit $1',
194
+ },
195
+ })
196
+ }
197
+ await mergeJson(this.base, config as PackageJson)
198
+ },
199
+ async when(): Promise<boolean> {
200
+ return (
201
+ (await isNpmPackage()) &&
202
+ ((await isYarnRoot()) || !(await isYarnSubModule()))
203
+ )
204
+ },
205
+ },
206
+ ]
207
+
208
+ async sync(): Promise<void> {
209
+ const { sync } = (await readJson(
210
+ path.resolve(this.base, 'package.json'),
211
+ )) as {
212
+ sync?: SyncConfigType[]
213
+ }
214
+ if (!sync) {
215
+ return
216
+ }
217
+ const syncConfigs = this.syncConfigs.filter((config) =>
218
+ sync.includes(config.type),
219
+ )
220
+ for (const syncConfig of syncConfigs) {
221
+ await syncConfig.handler()
222
+ }
223
+ }
224
+
225
+ async init(): Promise<void> {
226
+ const configMap = arrayToMap(
227
+ await AsyncArray.filter(this.syncConfigs, async (config) => {
228
+ if (!config.when) {
229
+ return true
230
+ }
231
+ return await config.when()
232
+ }),
233
+ (item) => item.type,
234
+ )
235
+ const res = await prompt<{
236
+ sync: string[]
237
+ }>({
238
+ type: 'multiselect',
239
+ message: '请选择需要同步的配置项',
240
+ name: 'sync',
241
+ choices: [...configMap.keys()],
242
+ })
243
+ await mergeJson(this.base, {
244
+ sync: res.sync,
245
+ })
246
+ }
247
+ }
@@ -0,0 +1,50 @@
1
+ import { mkdir, readJson, remove, writeJson } from 'fs-extra'
2
+ import path from 'path'
3
+ import { SyncConfigType, SyncProgram } from '../SyncProgram'
4
+ import { merge } from 'lodash-es'
5
+ import { PackageJson } from 'type-fest'
6
+
7
+ describe('测试 SyncProgram', () => {
8
+ const tempPath = path.resolve(__dirname, 'temp')
9
+ const syncProgram = new SyncProgram(tempPath)
10
+ beforeEach(async () => {
11
+ await remove(tempPath)
12
+ await mkdir(tempPath)
13
+ })
14
+ it('测试 sync', async () => {
15
+ const workspaces = ['packages/common/*', 'apps/*']
16
+ await writeJson(path.resolve(tempPath, 'lerna.json'), {})
17
+ await writeJson(path.resolve(tempPath, 'package.json'), {
18
+ name: 'temp',
19
+ license: 'mit',
20
+ private: true,
21
+ sync: [
22
+ 'prettier',
23
+ 'commitlint',
24
+ 'workspaces',
25
+ 'gitignore',
26
+ 'simplehooks',
27
+ 'eslint-ts',
28
+ 'jest',
29
+ ] as SyncConfigType[],
30
+ workspaces,
31
+ } as PackageJson)
32
+ await syncProgram.sync()
33
+ }, 100_000)
34
+
35
+ it('测试初始化同步配置', async () => {
36
+ await writeJson(path.resolve(tempPath, 'lerna.json'), {})
37
+ const file = path.resolve(tempPath, 'package.json')
38
+ await writeJson(file, {
39
+ name: 'temp',
40
+ private: true,
41
+ } as PackageJson)
42
+ await syncProgram.init()
43
+ expect(((await readJson(file)).sync as string[]).length).toBeGreaterThan(0)
44
+ }, 100_000)
45
+ })
46
+
47
+ it('测试 lodash-es.merge', () => {
48
+ const res = merge({ arr: ['a'] }, { arr: ['b'] })
49
+ expect(res).toEqual({ arr: ['b'] })
50
+ })
@@ -0,0 +1,46 @@
1
+ import {
2
+ isIncludeDep,
3
+ isNpmPackage,
4
+ isYarnRoot,
5
+ isYarnSubModule,
6
+ } from '../when'
7
+ import { pathExists, readJson } from 'fs-extra'
8
+ import { PackageJson } from 'type-fest'
9
+ import path from 'path'
10
+ import { findParent } from '../../../utils'
11
+
12
+ describe('测试 when', () => {
13
+ let rootPath: string
14
+ let subModulePath: string
15
+ beforeAll(async () => {
16
+ rootPath = (await findParent(__dirname, async (dir) => {
17
+ const jsonPath = path.resolve(dir, 'package.json')
18
+ return (
19
+ (await pathExists(jsonPath)) &&
20
+ !!((await readJson(jsonPath)) as PackageJson).workspaces
21
+ )
22
+ }))!
23
+ subModulePath = (await findParent(__dirname, async (dir) =>
24
+ pathExists(path.join(dir, 'package.json')),
25
+ ))!
26
+ })
27
+ it('测试 isNpmPackage', async () => {
28
+ expect(await isNpmPackage(subModulePath)).toBeTruthy()
29
+ expect(await isNpmPackage(rootPath)).toBeTruthy()
30
+ expect(await isNpmPackage(__dirname)).toBeFalsy()
31
+ })
32
+ it('测试 isYarnRoot', async () => {
33
+ expect(await isYarnRoot(rootPath)).toBeTruthy()
34
+ expect(await isYarnRoot(subModulePath)).toBeFalsy()
35
+ })
36
+ it('测试 isYarnSubModule', async () => {
37
+ expect(await isYarnSubModule(subModulePath)).toBeTruthy()
38
+ expect(await isYarnSubModule(rootPath)).toBeFalsy()
39
+ expect(await isYarnSubModule(__dirname)).toBeFalsy()
40
+ })
41
+ it('测试 isIncludeDep', async () => {
42
+ expect(
43
+ await isIncludeDep(['vue'], path.resolve(__dirname, 'temp')),
44
+ ).toBeFalsy()
45
+ })
46
+ })
@@ -0,0 +1,15 @@
1
+ import { Command } from 'commander'
2
+ import { SyncProgram } from './SyncProgram'
3
+
4
+ const syncProgram = new SyncProgram(process.cwd())
5
+ export const syncCommand = new Command('sync')
6
+ .description('同步配置')
7
+ .action(async () => {
8
+ await syncProgram.sync()
9
+ })
10
+ .addCommand(
11
+ new Command('init').description('初始化同步配置').action(async () => {
12
+ await syncProgram.init()
13
+ await syncProgram.sync()
14
+ }),
15
+ )
@@ -0,0 +1,67 @@
1
+ import { pathExists, readJson } from 'fs-extra'
2
+ import path from 'path'
3
+ import { PackageJson } from 'type-fest'
4
+ import { findParent } from '../../utils'
5
+
6
+ /**
7
+ * 判断是否包含 package.json
8
+ * @param cwd
9
+ */
10
+ export async function isNpmPackage(
11
+ cwd: string = process.cwd(),
12
+ ): Promise<boolean> {
13
+ return await pathExists(path.resolve(cwd, './package.json'))
14
+ }
15
+
16
+ /**
17
+ * 判断是 yarn2 monorepo 项目
18
+ * @param cwd
19
+ */
20
+ export async function isYarnRoot(
21
+ cwd: string = process.cwd(),
22
+ ): Promise<boolean> {
23
+ if (!(await isNpmPackage(cwd))) {
24
+ return false
25
+ }
26
+ const json = (await readJson(
27
+ path.resolve(cwd, './package.json'),
28
+ )) as PackageJson
29
+ return !!json.workspaces
30
+ }
31
+
32
+ /**
33
+ * 判断是 yarn2 monorepo 的子模块
34
+ */
35
+ export async function isYarnSubModule(
36
+ cwd: string = process.cwd(),
37
+ ): Promise<boolean> {
38
+ if (!(await isNpmPackage(cwd))) {
39
+ return false
40
+ }
41
+ //如果是 yarn monorepo 根模块则直接返回 true
42
+ if (await isYarnRoot(cwd)) {
43
+ return false
44
+ }
45
+ return (await findParent(path.dirname(cwd), isYarnRoot)) !== null
46
+ }
47
+
48
+ /**
49
+ * 是否包含指定依赖
50
+ * @param deps
51
+ * @param cwd
52
+ */
53
+ export async function isIncludeDep(
54
+ deps: string[],
55
+ cwd: string = process.cwd(),
56
+ ): Promise<boolean> {
57
+ if (!(await isNpmPackage(cwd))) {
58
+ return false
59
+ }
60
+ const json = (await readJson(
61
+ path.resolve(cwd, './package.json'),
62
+ )) as PackageJson
63
+ const set = new Set(
64
+ Object.keys({ ...json.dependencies, ...json.devDependencies }),
65
+ )
66
+ return deps.every((dep) => set.has(dep))
67
+ }