@liuli-util/cli 3.14.0 → 3.17.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +100 -80
- package/LICENSE +21 -0
- package/dist/bin.js +96 -77
- package/dist/bin.js.map +3 -3
- package/dist/commands/deploy/DeployService.d.ts +52 -0
- package/dist/commands/deploy/DeployService.d.ts.map +1 -0
- package/dist/commands/deploy/deploy.d.ts +3 -0
- package/dist/commands/deploy/deploy.d.ts.map +1 -0
- package/dist/commands/deploy/index.d.ts +3 -0
- package/dist/commands/deploy/index.d.ts.map +1 -0
- package/dist/commands/deploy/util/FileLock.d.ts +14 -0
- package/dist/commands/deploy/util/FileLock.d.ts.map +1 -0
- package/dist/commands/deploy/util/PromiseUtil.d.ts +14 -0
- package/dist/commands/deploy/util/PromiseUtil.d.ts.map +1 -0
- package/dist/commands/deploy/util/createArchive.d.ts +10 -0
- package/dist/commands/deploy/util/createArchive.d.ts.map +1 -0
- package/dist/commands/deploy/util/execPromise.d.ts +4 -0
- package/dist/commands/deploy/util/execPromise.d.ts.map +1 -0
- package/dist/commands/deploy/util/wait.d.ts +9 -0
- package/dist/commands/deploy/util/wait.d.ts.map +1 -0
- package/dist/commands/esbuild/ESBuildProgram.d.ts.map +1 -1
- package/dist/commands/generate/GenerateProgram.d.ts.map +1 -1
- package/dist/commands/sync/SyncProgram.d.ts.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +2 -2
- package/dist/utils/nodeCacheDir.d.ts +2 -0
- package/dist/utils/nodeCacheDir.d.ts.map +1 -0
- package/package.json +73 -66
- package/src/bin.ts +3 -2
- package/src/commands/deploy/DeployService.ts +174 -0
- package/src/commands/deploy/__tests__/DeployService.test.ts +75 -0
- package/src/commands/deploy/__tests__/FileLock.test.ts +27 -0
- package/src/commands/deploy/__tests__/conf.test.ts +29 -0
- package/src/commands/deploy/__tests__/simpleGit.test.ts +34 -0
- package/src/commands/deploy/__tests__/util/deployGhPageWorker.ts +16 -0
- package/src/commands/deploy/deploy.ts +48 -0
- package/src/commands/deploy/index.ts +8 -0
- package/src/commands/deploy/util/FileLock.ts +31 -0
- package/src/commands/deploy/util/PromiseUtil.ts +34 -0
- package/src/commands/deploy/util/createArchive.ts +30 -0
- package/src/commands/deploy/util/execPromise.ts +13 -0
- package/src/commands/deploy/util/wait.ts +23 -0
- package/src/commands/esbuild/ESBuildProgram.ts +1 -1
- package/src/commands/esbuild/__tests__/.temp/getDeps/package.json +1 -0
- package/src/commands/generate/__tests__/.temp/test-cli/CHANGELOG.md +1 -0
- package/src/commands/generate/__tests__/.temp/test-cli/README.md +1 -0
- package/src/commands/generate/__tests__/.temp/test-cli/bin.js +3 -0
- package/src/commands/generate/__tests__/.temp/test-cli/package.json +44 -0
- package/src/commands/generate/__tests__/.temp/test-cli/src/bin.ts +13 -0
- package/src/commands/generate/__tests__/.temp/test-cli/src/index.ts +1 -0
- package/src/commands/generate/__tests__/.temp/test-cli/tsconfig.json +28 -0
- package/src/commands/sync/SyncProgram.ts +15 -41
- package/src/commands/sync/__tests__/SyncProgram.test.ts +13 -1
- package/src/utils/__tests__/nodeCacheDir.test.ts +6 -0
- package/src/utils/nodeCacheDir.ts +54 -0
- package/templates/cli/package.json +4 -10
- package/templates/cli/tsconfig.json +28 -28
- package/templates/lib/package.json +2 -8
- package/templates/lib/tsconfig.json +28 -28
- package/tsconfig.json +34 -34
- package/src/commands/sync/__tests__/.temp/lerna.json +0 -6
- package/src/commands/sync/__tests__/.temp/package.json +0 -58
@@ -0,0 +1,75 @@
|
|
1
|
+
import * as path from 'path'
|
2
|
+
import { mkdirp, remove, writeFile } from 'fs-extra'
|
3
|
+
import { GhPagesDeployService, SftpDeployOptions, SftpDeployService } from '../DeployService'
|
4
|
+
import { execPromise } from '../util/execPromise'
|
5
|
+
|
6
|
+
const tempPath = path.resolve(__dirname, '.temp')
|
7
|
+
beforeAll(async () => {
|
8
|
+
const distPath = path.resolve(tempPath, 'dist')
|
9
|
+
await remove(tempPath)
|
10
|
+
await mkdirp(distPath)
|
11
|
+
|
12
|
+
await writeFile(
|
13
|
+
path.resolve(distPath, 'index.html'),
|
14
|
+
`<!DOCTYPE html>
|
15
|
+
<html lang="en">
|
16
|
+
<head>
|
17
|
+
<meta charset="UTF-8" />
|
18
|
+
<title>测试部署</title>
|
19
|
+
</head>
|
20
|
+
<body>
|
21
|
+
<h1>测试部署</h1>
|
22
|
+
</body>
|
23
|
+
</html>
|
24
|
+
`,
|
25
|
+
)
|
26
|
+
})
|
27
|
+
|
28
|
+
describe('测试 SftpDeployService', () => {
|
29
|
+
const options: SftpDeployOptions = {
|
30
|
+
cwd: tempPath,
|
31
|
+
dest: 'dist',
|
32
|
+
remote: '/home/pinefield/apps/test',
|
33
|
+
sshConfig: {
|
34
|
+
host: '10.8.2.4',
|
35
|
+
username: 'pinefield',
|
36
|
+
},
|
37
|
+
}
|
38
|
+
it('基本示例', async () => {
|
39
|
+
const sftpDeployService = new SftpDeployService(options)
|
40
|
+
await sftpDeployService.deploy()
|
41
|
+
})
|
42
|
+
describe('测试校验', () => {
|
43
|
+
it('正确情况', () => {
|
44
|
+
const [isValid] = new SftpDeployService(options).validate()
|
45
|
+
expect(isValid).toBeTruthy()
|
46
|
+
})
|
47
|
+
it('缺少字段', () => {
|
48
|
+
const [isValid, errorText] = new SftpDeployService({
|
49
|
+
cwd: tempPath,
|
50
|
+
} as SftpDeployOptions).validate()
|
51
|
+
expect(isValid).toBeFalsy()
|
52
|
+
console.log(errorText)
|
53
|
+
})
|
54
|
+
})
|
55
|
+
})
|
56
|
+
|
57
|
+
describe('测试 GhPagesDeployService', () => {
|
58
|
+
const ghPagesDeployService = new GhPagesDeployService({
|
59
|
+
cwd: tempPath,
|
60
|
+
dest: 'dist',
|
61
|
+
remote: 'examples/test-app',
|
62
|
+
})
|
63
|
+
it('基本示例', async () => {
|
64
|
+
await ghPagesDeployService.deploy().on('process', (title) => console.log(title))
|
65
|
+
}, 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
|
+
)
|
74
|
+
}, 10_000)
|
75
|
+
})
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import * as path from 'path'
|
2
|
+
import { FileLock } from '../util/FileLock'
|
3
|
+
import { AsyncArray } from '../../../utils'
|
4
|
+
import { wait } from '../util/wait'
|
5
|
+
|
6
|
+
describe('测试文件锁', () => {
|
7
|
+
const tempPath = path.resolve(__dirname, '.temp/test.lock')
|
8
|
+
it('基本示例', async () => {
|
9
|
+
const fileLock = new FileLock(tempPath)
|
10
|
+
expect(await fileLock.lock()).toBeTruthy()
|
11
|
+
expect(await fileLock.lock()).toBeFalsy()
|
12
|
+
await fileLock.unlock()
|
13
|
+
expect(await fileLock.lock()).toBeTruthy()
|
14
|
+
await fileLock.unlock()
|
15
|
+
})
|
16
|
+
it('测试并发调用', async () => {
|
17
|
+
const start = Date.now()
|
18
|
+
await AsyncArray.forEach(Array(10).fill(0), async () => {
|
19
|
+
const fileLock = new FileLock(tempPath)
|
20
|
+
await wait(() => fileLock.lock())
|
21
|
+
await wait(100)
|
22
|
+
await fileLock.unlock()
|
23
|
+
})
|
24
|
+
const time = Date.now() - start
|
25
|
+
expect(time).toBeLessThan(3000)
|
26
|
+
})
|
27
|
+
})
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import Conf from 'conf'
|
2
|
+
import { wait } from '../util/wait'
|
3
|
+
import { writeJson } from 'fs-extra'
|
4
|
+
import * as path from 'path'
|
5
|
+
|
6
|
+
describe('测试 conf', () => {
|
7
|
+
const conf = new Conf<{ lock: boolean }>({ projectName: '@liuli-util/test' })
|
8
|
+
beforeEach(() => {
|
9
|
+
conf.clear()
|
10
|
+
})
|
11
|
+
it('测试并发模式', async () => {
|
12
|
+
await Promise.all([
|
13
|
+
wait(() => conf.store.lock),
|
14
|
+
wait(1000).then(() => {
|
15
|
+
conf.set('lock', true)
|
16
|
+
}),
|
17
|
+
])
|
18
|
+
expect(conf.store.lock).toBeTruthy()
|
19
|
+
})
|
20
|
+
it('测试直接修改文件', async () => {
|
21
|
+
await Promise.all([
|
22
|
+
wait(() => conf.store.lock),
|
23
|
+
wait(1000).then(async () => {
|
24
|
+
await writeJson(conf.path, { lock: true })
|
25
|
+
}),
|
26
|
+
])
|
27
|
+
expect(conf.store.lock).toBeTruthy()
|
28
|
+
})
|
29
|
+
})
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import simpleGit, { SimpleGit } from 'simple-git'
|
2
|
+
import * as path from 'path'
|
3
|
+
import { mkdirp, remove } from 'fs-extra'
|
4
|
+
|
5
|
+
describe('测试 simple-git', () => {
|
6
|
+
async function getOriginRemote() {
|
7
|
+
const git = simpleGit()
|
8
|
+
const remotes = await git.getRemotes(true)
|
9
|
+
return remotes.find((item) => item.name === 'origin')
|
10
|
+
}
|
11
|
+
let git: SimpleGit
|
12
|
+
const tempPath = path.resolve(__dirname, '.temp')
|
13
|
+
beforeEach(async () => {
|
14
|
+
git = simpleGit()
|
15
|
+
await remove(tempPath)
|
16
|
+
await mkdirp(tempPath)
|
17
|
+
})
|
18
|
+
it('测试获取当前项目的远端地址', async () => {
|
19
|
+
const originRemote = await getOriginRemote()
|
20
|
+
console.log('git.getRemotes', originRemote)
|
21
|
+
expect(originRemote).not.toBeUndefined()
|
22
|
+
})
|
23
|
+
it('克隆项目', async () => {
|
24
|
+
const originRemote = (await getOriginRemote())!
|
25
|
+
console.log('获取当前项目远端配置: ', originRemote)
|
26
|
+
const originRepoName = originRemote.refs.fetch.replace(new RegExp('[/:]', 'g'), '_')
|
27
|
+
await git.clone(originRemote.refs.fetch, path.resolve(tempPath, originRepoName), { '--branch': 'gh-pages' })
|
28
|
+
}, 100_000)
|
29
|
+
it('测试 git status', async () => {
|
30
|
+
await git.add('-A')
|
31
|
+
const status = await git.status()
|
32
|
+
console.log('status: ', status.files)
|
33
|
+
})
|
34
|
+
})
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { GhPagesDeployService } from '../../DeployService'
|
2
|
+
import * as path from 'path'
|
3
|
+
|
4
|
+
async function deployGhPages() {
|
5
|
+
const tempPath = path.resolve(__dirname, '../.temp/')
|
6
|
+
const ghPagesDeployService = new GhPagesDeployService({
|
7
|
+
cwd: tempPath,
|
8
|
+
dest: 'dist',
|
9
|
+
remote: 'examples/test-app',
|
10
|
+
})
|
11
|
+
const now = Date.now()
|
12
|
+
await ghPagesDeployService.deploy().on('process', (title) => console.log(`[${now}] ${title}`))
|
13
|
+
}
|
14
|
+
|
15
|
+
// noinspection JSIgnoredPromiseFromCall
|
16
|
+
deployGhPages()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import {
|
2
|
+
BaseDeployOptions,
|
3
|
+
DeployTypeEnum,
|
4
|
+
GhPagesDeployService,
|
5
|
+
IDeployService,
|
6
|
+
SftpDeployOptions,
|
7
|
+
SftpDeployService,
|
8
|
+
} from './DeployService'
|
9
|
+
import * as path from 'path'
|
10
|
+
import { pathExists, readJson } from 'fs-extra'
|
11
|
+
|
12
|
+
async function getOptions(cwd: string): Promise<BaseDeployOptions> {
|
13
|
+
const pkgJsonPath = path.resolve(cwd, 'package.json')
|
14
|
+
if (await pathExists(pkgJsonPath)) {
|
15
|
+
const config = (await readJson(pkgJsonPath)).deploy
|
16
|
+
if (!config) {
|
17
|
+
throw new Error('找不到配置')
|
18
|
+
}
|
19
|
+
return config
|
20
|
+
}
|
21
|
+
throw new Error('找不到配置')
|
22
|
+
}
|
23
|
+
|
24
|
+
export async function deploy(options: Omit<BaseDeployOptions, 'type'>): Promise<void> {
|
25
|
+
const deployOptions = await getOptions(options.cwd)
|
26
|
+
let service: IDeployService
|
27
|
+
const _options = {
|
28
|
+
...options,
|
29
|
+
...deployOptions,
|
30
|
+
} as unknown as SftpDeployOptions
|
31
|
+
switch (deployOptions.type) {
|
32
|
+
case DeployTypeEnum.Sftp:
|
33
|
+
service = new SftpDeployService(_options)
|
34
|
+
break
|
35
|
+
case DeployTypeEnum.GhPages:
|
36
|
+
service = new GhPagesDeployService(_options)
|
37
|
+
break
|
38
|
+
default:
|
39
|
+
throw new Error('未知的部署预设类型 ' + deployOptions.type)
|
40
|
+
}
|
41
|
+
const [isValidate, errorText] = service.validate()
|
42
|
+
if (!isValidate) {
|
43
|
+
throw new Error(errorText)
|
44
|
+
}
|
45
|
+
await service.deploy().on('process', (title) => {
|
46
|
+
console.info(title)
|
47
|
+
})
|
48
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { Command } from 'commander'
|
2
|
+
import { deploy } from './deploy'
|
3
|
+
import * as path from 'path'
|
4
|
+
|
5
|
+
export const deployCommand = new Command('deploy')
|
6
|
+
.description('部署项目到远端')
|
7
|
+
.option('--debug', '是否开启调试模式')
|
8
|
+
.action((options: { debug?: boolean }) => deploy({ cwd: path.resolve(), debug: !!options.debug }))
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { close, open, remove } from 'fs-extra'
|
2
|
+
import path from 'path'
|
3
|
+
|
4
|
+
export class FileLock {
|
5
|
+
constructor(private readonly lockFilePath: string) {}
|
6
|
+
|
7
|
+
private lockId?: number
|
8
|
+
|
9
|
+
/**
|
10
|
+
* 加锁
|
11
|
+
*/
|
12
|
+
async lock(): Promise<boolean> {
|
13
|
+
try {
|
14
|
+
this.lockId = await open(path.resolve(this.lockFilePath), 'wx')
|
15
|
+
return true
|
16
|
+
} catch (e) {
|
17
|
+
return false
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* 解锁
|
23
|
+
*/
|
24
|
+
async unlock(): Promise<void> {
|
25
|
+
if (!this.lockId) {
|
26
|
+
throw new Error('未加锁')
|
27
|
+
}
|
28
|
+
await remove(path.resolve(this.lockFilePath))
|
29
|
+
close(this.lockId)
|
30
|
+
}
|
31
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { ConditionalKeys, PromiseValue } from 'type-fest'
|
2
|
+
|
3
|
+
type VoidFunc = ((...args: any[]) => void) | undefined
|
4
|
+
|
5
|
+
export type EventExtPromise<T, E> = Promise<T> & {
|
6
|
+
on<K extends ConditionalKeys<E, VoidFunc>>(type: K, callback: E[K]): EventExtPromise<T, E>
|
7
|
+
}
|
8
|
+
|
9
|
+
export class PromiseUtil {
|
10
|
+
/**
|
11
|
+
* 创建一个支持 on* 事件的 Promise 实例
|
12
|
+
* @param executor
|
13
|
+
*/
|
14
|
+
static wrapOnEvent<
|
15
|
+
F extends (events: any) => Promise<any>,
|
16
|
+
E extends Parameters<F>[0],
|
17
|
+
K extends ConditionalKeys<E, VoidFunc>,
|
18
|
+
>(executor: F): EventExtPromise<PromiseValue<ReturnType<F>>, E> {
|
19
|
+
const events: Partial<Pick<E, K>> = {}
|
20
|
+
const res = new Promise(async (resolve, reject) => {
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
22
|
+
try {
|
23
|
+
resolve(await executor(events))
|
24
|
+
} catch (e) {
|
25
|
+
reject(e)
|
26
|
+
}
|
27
|
+
})
|
28
|
+
Reflect.set(res, 'on', (type: K, callback: E[K]) => {
|
29
|
+
events[type] = callback
|
30
|
+
return res
|
31
|
+
})
|
32
|
+
return res as any
|
33
|
+
}
|
34
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { create, CreateOptions } from 'tar'
|
2
|
+
import { promise } from 'glob-promise'
|
3
|
+
import * as path from 'path'
|
4
|
+
|
5
|
+
export type ArchiveOptions = {
|
6
|
+
// sourceDir 源目录,一般设置为 dist
|
7
|
+
sourceDir: string
|
8
|
+
// destPath 目标位置,可能是 <packageName>.jpl
|
9
|
+
destPath: string
|
10
|
+
}
|
11
|
+
|
12
|
+
/**
|
13
|
+
* 创建 jpl 压缩文件
|
14
|
+
* @param options
|
15
|
+
*/
|
16
|
+
export async function createArchive(options: ArchiveOptions): Promise<void> {
|
17
|
+
const sourceDir = path.resolve(options.sourceDir)
|
18
|
+
const destPath = path.resolve(options.destPath)
|
19
|
+
const distFiles = (await promise(`${sourceDir}/**/*`, { nodir: true })).map((f) => f.substr(sourceDir.length + 1))
|
20
|
+
await create(
|
21
|
+
{
|
22
|
+
strict: true,
|
23
|
+
portable: true,
|
24
|
+
file: destPath,
|
25
|
+
cwd: sourceDir,
|
26
|
+
sync: true,
|
27
|
+
} as Partial<CreateOptions>,
|
28
|
+
distFiles,
|
29
|
+
)
|
30
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { exec, ExecOptions } from 'child_process'
|
2
|
+
|
3
|
+
export function execPromise(command: string, options?: ExecOptions): Promise<string | Buffer> {
|
4
|
+
return new Promise((resolve, reject) => {
|
5
|
+
exec(command, options, (error, stdout) => {
|
6
|
+
if (error) {
|
7
|
+
reject(error)
|
8
|
+
return
|
9
|
+
}
|
10
|
+
resolve(stdout)
|
11
|
+
})
|
12
|
+
})
|
13
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
/**
|
2
|
+
* 等待指定的时间/等待指定表达式成立
|
3
|
+
* 如果未指定等待条件则立刻执行
|
4
|
+
* 注: 此实现在 nodejs 10- 会存在宏任务与微任务的问题,切记 async-await 本质上还是 Promise 的语法糖,实际上并非真正的同步函数!!!即便在浏览器,也不要依赖于这种特性。
|
5
|
+
* @param param 等待时间/等待条件
|
6
|
+
* @returns Promise 对象
|
7
|
+
*/
|
8
|
+
export function wait(param?: number | (() => boolean | Promise<boolean>)): Promise<void> {
|
9
|
+
return new Promise((resolve) => {
|
10
|
+
if (typeof param === 'number') {
|
11
|
+
setTimeout(resolve, param)
|
12
|
+
} else if (typeof param === 'function') {
|
13
|
+
const timer = setInterval(async () => {
|
14
|
+
if (await param()) {
|
15
|
+
clearInterval(timer)
|
16
|
+
resolve()
|
17
|
+
}
|
18
|
+
}, 100)
|
19
|
+
} else {
|
20
|
+
resolve()
|
21
|
+
}
|
22
|
+
})
|
23
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"devDependencies":{"@types/node":"16"},"dependencies":{"ora":"^6"},"peerDependencies":{"typescript":"^4"}}
|
@@ -0,0 +1 @@
|
|
1
|
+
# CHANGELOG
|
@@ -0,0 +1 @@
|
|
1
|
+
# @liuli-util/cli-test-cli
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"name": "test-cli",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"main": "dist/index.js",
|
5
|
+
"module": "dist/index.esm.js",
|
6
|
+
"types": "dist/index.d.ts",
|
7
|
+
"license": "MIT",
|
8
|
+
"scripts": {
|
9
|
+
"build": "rimraf dist && liuli-cli build cli",
|
10
|
+
"dev": "liuli-cli build cli -w",
|
11
|
+
"start": "esno src/bin.ts",
|
12
|
+
"docs:server": "live-server docs",
|
13
|
+
"docs:dev": "typedoc --watch",
|
14
|
+
"docs:build": "rimraf docs && typedoc",
|
15
|
+
"docs:deploy": "yarn docs:build && gh-pages -d docs/ -e / -a"
|
16
|
+
},
|
17
|
+
"bin": {
|
18
|
+
"cli-name": "./bin.js"
|
19
|
+
},
|
20
|
+
"jest": {
|
21
|
+
"preset": "ts-jest"
|
22
|
+
},
|
23
|
+
"dependencies": {
|
24
|
+
"commander": "^8.2.0",
|
25
|
+
"fs-extra": "^10.0.0",
|
26
|
+
"inquirer": "^8.1.5"
|
27
|
+
},
|
28
|
+
"devDependencies": {
|
29
|
+
"@liuli-util/cli": "^3.11.1",
|
30
|
+
"@types/fs-extra": "^9.0.13",
|
31
|
+
"@types/inquirer": "^8.1.2",
|
32
|
+
"@types/jest": "^27.0.2",
|
33
|
+
"@types/lodash": "^4.14.173",
|
34
|
+
"@types/node": "^16.9.6",
|
35
|
+
"esno": "^0.9.1",
|
36
|
+
"gh-pages": "^3.2.3",
|
37
|
+
"jest": "^27.2.1",
|
38
|
+
"rimraf": "^3.0.2",
|
39
|
+
"ts-jest": "^27.0.5",
|
40
|
+
"type-fest": "^2.3.4",
|
41
|
+
"typedoc": "^0.22.4",
|
42
|
+
"typescript": "^4.4.3"
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { Command } from 'commander'
|
2
|
+
import { prompt } from 'inquirer'
|
3
|
+
|
4
|
+
new Command()
|
5
|
+
.action(async () => {
|
6
|
+
const { name } = await prompt<{ name: string }>({
|
7
|
+
type: 'input',
|
8
|
+
name: 'name',
|
9
|
+
message: '请输入名字',
|
10
|
+
})
|
11
|
+
console.log(`hello ${name}`)
|
12
|
+
})
|
13
|
+
.parse()
|
@@ -0,0 +1 @@
|
|
1
|
+
export {}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "ESNext",
|
4
|
+
"lib": [
|
5
|
+
"ESNext"
|
6
|
+
],
|
7
|
+
"outDir": "./dist",
|
8
|
+
"skipLibCheck": true,
|
9
|
+
"esModuleInterop": true,
|
10
|
+
"strict": true,
|
11
|
+
"module": "ESNext",
|
12
|
+
"moduleResolution": "node",
|
13
|
+
"sourceMap": true,
|
14
|
+
"declaration": true,
|
15
|
+
"declarationMap": true
|
16
|
+
},
|
17
|
+
"include": [
|
18
|
+
"src"
|
19
|
+
],
|
20
|
+
"typedocOptions": {
|
21
|
+
"entryPoints": [
|
22
|
+
"src/index.ts"
|
23
|
+
],
|
24
|
+
"out": "docs",
|
25
|
+
"readme": "README.md",
|
26
|
+
"gitRemote": "origin"
|
27
|
+
}
|
28
|
+
}
|
@@ -13,13 +13,9 @@ import { PathUtil } from '../../PathUtil'
|
|
13
13
|
|
14
14
|
export async function mergeJson(base: string, json: object): Promise<void> {
|
15
15
|
const pkgJsonFilePath = path.resolve(base, './package.json')
|
16
|
-
await writeJson(
|
17
|
-
|
18
|
-
|
19
|
-
{
|
20
|
-
spaces: 2,
|
21
|
-
},
|
22
|
-
)
|
16
|
+
await writeJson(pkgJsonFilePath, merge(await readJson(pkgJsonFilePath), json), {
|
17
|
+
spaces: 2,
|
18
|
+
})
|
23
19
|
}
|
24
20
|
|
25
21
|
export type SyncConfigType =
|
@@ -68,10 +64,7 @@ export class SyncProgram {
|
|
68
64
|
} as PackageJson)
|
69
65
|
},
|
70
66
|
async when(): Promise<boolean> {
|
71
|
-
return (
|
72
|
-
(await isNpmPackage()) &&
|
73
|
-
((await isYarnRoot()) || !(await isYarnSubModule()))
|
74
|
-
)
|
67
|
+
return (await isNpmPackage()) && ((await isYarnRoot()) || !(await isYarnSubModule()))
|
75
68
|
},
|
76
69
|
},
|
77
70
|
{
|
@@ -91,23 +84,14 @@ export class SyncProgram {
|
|
91
84
|
} as PackageJson)
|
92
85
|
},
|
93
86
|
async when(): Promise<boolean> {
|
94
|
-
return (
|
95
|
-
(await isNpmPackage()) &&
|
96
|
-
((await isYarnRoot()) || !(await isYarnSubModule()))
|
97
|
-
)
|
87
|
+
return (await isNpmPackage()) && ((await isYarnRoot()) || !(await isYarnSubModule()))
|
98
88
|
},
|
99
89
|
},
|
100
90
|
{
|
101
91
|
type: 'gitignore',
|
102
92
|
handler: async () => {
|
103
93
|
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
|
-
)
|
94
|
+
await writeFile(gitignorePath, await readFile(path.resolve(PathUtil.RootPath, '_gitignore'), 'utf-8'))
|
111
95
|
},
|
112
96
|
},
|
113
97
|
{
|
@@ -123,11 +107,7 @@ export class SyncProgram {
|
|
123
107
|
} as PackageJson)
|
124
108
|
},
|
125
109
|
async when(): Promise<boolean> {
|
126
|
-
return (
|
127
|
-
(await isNpmPackage()) &&
|
128
|
-
!(await isIncludeDep(['vue'])) &&
|
129
|
-
!(await isIncludeDep(['react']))
|
130
|
-
)
|
110
|
+
return (await isNpmPackage()) && !(await isIncludeDep(['vue'])) && !(await isIncludeDep(['react']))
|
131
111
|
},
|
132
112
|
},
|
133
113
|
{
|
@@ -154,6 +134,10 @@ export class SyncProgram {
|
|
154
134
|
preset: 'ts-jest',
|
155
135
|
testMatch: ['<rootDir>/src/**/__tests__/*.test.ts'],
|
156
136
|
},
|
137
|
+
devDependencies: {
|
138
|
+
jest: '^27.4.3',
|
139
|
+
'ts-jest': '^27.0.7',
|
140
|
+
},
|
157
141
|
})
|
158
142
|
},
|
159
143
|
},
|
@@ -171,10 +155,7 @@ export class SyncProgram {
|
|
171
155
|
}
|
172
156
|
let config = {
|
173
157
|
scripts: {
|
174
|
-
postinstall: appendScript(
|
175
|
-
json?.scripts?.postinstall,
|
176
|
-
'npx simple-git-hooks',
|
177
|
-
),
|
158
|
+
postinstall: appendScript(json?.scripts?.postinstall, 'npx simple-git-hooks'),
|
178
159
|
},
|
179
160
|
'simple-git-hooks': {
|
180
161
|
'pre-commit': 'yarn lint-staged',
|
@@ -197,26 +178,19 @@ export class SyncProgram {
|
|
197
178
|
await mergeJson(this.base, config as PackageJson)
|
198
179
|
},
|
199
180
|
async when(): Promise<boolean> {
|
200
|
-
return (
|
201
|
-
(await isNpmPackage()) &&
|
202
|
-
((await isYarnRoot()) || !(await isYarnSubModule()))
|
203
|
-
)
|
181
|
+
return (await isNpmPackage()) && ((await isYarnRoot()) || !(await isYarnSubModule()))
|
204
182
|
},
|
205
183
|
},
|
206
184
|
]
|
207
185
|
|
208
186
|
async sync(): Promise<void> {
|
209
|
-
const { sync } = (await readJson(
|
210
|
-
path.resolve(this.base, 'package.json'),
|
211
|
-
)) as {
|
187
|
+
const { sync } = (await readJson(path.resolve(this.base, 'package.json'))) as {
|
212
188
|
sync?: SyncConfigType[]
|
213
189
|
}
|
214
190
|
if (!sync) {
|
215
191
|
return
|
216
192
|
}
|
217
|
-
const syncConfigs = this.syncConfigs.filter((config) =>
|
218
|
-
sync.includes(config.type),
|
219
|
-
)
|
193
|
+
const syncConfigs = this.syncConfigs.filter((config) => sync.includes(config.type))
|
220
194
|
for (const syncConfig of syncConfigs) {
|
221
195
|
await syncConfig.handler()
|
222
196
|
}
|
@@ -30,7 +30,7 @@ describe('测试 SyncProgram', () => {
|
|
30
30
|
workspaces,
|
31
31
|
} as PackageJson)
|
32
32
|
await syncProgram.sync()
|
33
|
-
}
|
33
|
+
})
|
34
34
|
|
35
35
|
it.skip('测试初始化同步配置', async () => {
|
36
36
|
await writeJson(path.resolve(tempPath, 'lerna.json'), {})
|
@@ -42,6 +42,18 @@ describe('测试 SyncProgram', () => {
|
|
42
42
|
await syncProgram.init()
|
43
43
|
expect(((await readJson(file)).sync as string[]).length).toBeGreaterThan(0)
|
44
44
|
}, 100_000)
|
45
|
+
|
46
|
+
it('测试同步 jest', async () => {
|
47
|
+
const jsonPath = path.resolve(tempPath, 'package.json')
|
48
|
+
await writeJson(jsonPath, {
|
49
|
+
name: 'temp',
|
50
|
+
sync: ['jest'] as SyncConfigType[],
|
51
|
+
} as PackageJson)
|
52
|
+
await syncProgram.sync()
|
53
|
+
const json = (await readJson(jsonPath)) as PackageJson
|
54
|
+
const devDeps = Object.keys(json.devDependencies!)
|
55
|
+
expect(devDeps.includes('jest') && devDeps.includes('ts-jest')).toBeTruthy()
|
56
|
+
})
|
45
57
|
})
|
46
58
|
|
47
59
|
it('测试 lodash-es.merge', () => {
|