@liuli-util/cli 3.13.1 → 3.17.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +100 -74
- 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 +1 -2
- 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 +3 -12
- package/src/commands/esbuild/__tests__/.temp/getDeps/package.json +1 -0
- package/src/commands/esbuild/__tests__/ESBuildProgram.test.ts +0 -1
- package/src/commands/generate/GenerateProgram.ts +3 -1
- 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/generate/__tests__/GenerateProgram.test.ts +17 -17
- 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
|
+
}
|
@@ -31,7 +31,7 @@ export class ESBuildProgram {
|
|
31
31
|
this.options.isWatch = isWatch
|
32
32
|
}
|
33
33
|
|
34
|
-
static readonly globalExternal = ['esbuild', 'pnpapi', 'ts-morph']
|
34
|
+
static readonly globalExternal = ['esbuild', 'pnpapi', 'ts-morph', 'ssh2']
|
35
35
|
|
36
36
|
/**
|
37
37
|
* 获取所有依赖
|
@@ -146,22 +146,14 @@ export class ESBuildProgram {
|
|
146
146
|
* @param platform
|
147
147
|
* @param plugins
|
148
148
|
*/
|
149
|
-
getBuildIifeOption({
|
150
|
-
deps,
|
151
|
-
platform,
|
152
|
-
globalName,
|
153
|
-
}: {
|
154
|
-
deps: string[]
|
155
|
-
platform: Platform
|
156
|
-
globalName: string
|
157
|
-
}): BuildOptions {
|
149
|
+
getBuildIifeOption({ platform, globalName }: { platform: Platform; globalName: string }): BuildOptions {
|
158
150
|
return {
|
159
151
|
entryPoints: [path.resolve(this.options.base, './src/index.ts')],
|
160
152
|
outfile: path.resolve(this.options.base, './dist/index.iife.js'),
|
161
153
|
format: 'iife',
|
162
154
|
sourcemap: true,
|
163
155
|
bundle: true,
|
164
|
-
external: [...ESBuildProgram.globalExternal
|
156
|
+
external: [...ESBuildProgram.globalExternal],
|
165
157
|
platform: platform,
|
166
158
|
minify: !this.options.isWatch,
|
167
159
|
incremental: this.options.isWatch,
|
@@ -239,7 +231,6 @@ export class ESBuildProgram {
|
|
239
231
|
task: async () => {
|
240
232
|
return await this.build(
|
241
233
|
this.getBuildIifeOption({
|
242
|
-
deps: deps,
|
243
234
|
platform: platform,
|
244
235
|
globalName: getPkgGlobalName(
|
245
236
|
((await readJson(path.resolve(this.options.base, './package.json'))) as PackageJson).name!,
|
@@ -0,0 +1 @@
|
|
1
|
+
{"devDependencies":{"@types/node":"16"},"dependencies":{"ora":"^6"},"peerDependencies":{"typescript":"^4"}}
|
@@ -64,7 +64,9 @@ export class GenerateProgram {
|
|
64
64
|
}
|
65
65
|
}
|
66
66
|
await remove(destFile)
|
67
|
-
await copy(srcFile, destFile
|
67
|
+
await copy(srcFile, destFile, {
|
68
|
+
filter: (source) => path.basename(source) !== 'node_modules',
|
69
|
+
})
|
68
70
|
await GenerateProgram.updatePackageJSON(destFile)
|
69
71
|
await GenerateProgram.updateReadme(destFile)
|
70
72
|
if (config.initSync) {
|
@@ -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
|
+
}
|
@@ -1,19 +1,11 @@
|
|
1
1
|
import { GenerateProgram, TemplateTypeEnum } from '../GenerateProgram'
|
2
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'
|
3
|
+
import { mkdir, pathExists, readFile, readJson, remove, writeFile, writeJson } from 'fs-extra'
|
12
4
|
import { PackageJson } from 'type-fest'
|
13
5
|
|
14
6
|
describe('测试 InitProgram', () => {
|
15
7
|
const initProgram = new GenerateProgram()
|
16
|
-
const tempPath = path.resolve(__dirname, 'temp')
|
8
|
+
const tempPath = path.resolve(__dirname, '.temp')
|
17
9
|
it('生成项目', async () => {
|
18
10
|
const dest = path.resolve(tempPath, 'lib-demo')
|
19
11
|
await remove(dest)
|
@@ -22,7 +14,7 @@ describe('测试 InitProgram', () => {
|
|
22
14
|
dest,
|
23
15
|
})
|
24
16
|
expect(await pathExists(dest)).toBeTruthy()
|
25
|
-
}
|
17
|
+
})
|
26
18
|
describe('测试一些钩子', () => {
|
27
19
|
beforeAll(async () => {
|
28
20
|
await remove(tempPath)
|
@@ -34,18 +26,26 @@ describe('测试 InitProgram', () => {
|
|
34
26
|
name: '@liuli-util/template',
|
35
27
|
})
|
36
28
|
await GenerateProgram.updatePackageJSON(tempPath)
|
37
|
-
expect(
|
38
|
-
((await readJson(jsonPath)) as PackageJson).name?.endsWith('temp'),
|
39
|
-
).toBeTruthy()
|
29
|
+
expect(((await readJson(jsonPath)) as PackageJson).name?.endsWith('temp')).toBeTruthy()
|
40
30
|
})
|
41
31
|
it('测试修改 readme', async () => {
|
42
32
|
const readmePath = path.resolve(tempPath, 'README.md')
|
43
33
|
await writeFile(readmePath, `# @liuli-util/template`)
|
44
34
|
await GenerateProgram.updateReadme(tempPath)
|
45
35
|
console.log('readme: ', await readFile(readmePath, 'utf-8'))
|
46
|
-
expect(
|
47
|
-
|
48
|
-
|
36
|
+
expect((await readFile(readmePath, 'utf-8')).endsWith('temp')).toBeTruthy()
|
37
|
+
})
|
38
|
+
})
|
39
|
+
describe('测试错误修复', () => {
|
40
|
+
it('修复 monorepo 中使用 cli 会复制 node_modules 的错误', async () => {
|
41
|
+
const program = new GenerateProgram()
|
42
|
+
const destPath = path.resolve(tempPath, 'test-cli')
|
43
|
+
await program.generate({
|
44
|
+
template: TemplateTypeEnum.Cli,
|
45
|
+
dest: destPath,
|
46
|
+
initSync: false,
|
47
|
+
})
|
48
|
+
expect(await pathExists(path.resolve(destPath, 'node_modules'))).toBeFalsy()
|
49
49
|
})
|
50
50
|
})
|
51
51
|
})
|