mixcli 3.2.0 → 3.2.2
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +67 -94
- package/dist/index.d.ts +67 -94
- package/dist/index.js +3 -1132
- package/dist/index.js.map +1 -1
- package/package.json +24 -11
- package/readme.md +8 -12
- package/src/cli.ts +8 -15
- package/src/command.ts +452 -452
- package/src/finder.ts +142 -142
- package/src/index.ts +5 -4
- package/src/option.ts +39 -81
- package/src/prompt.ts +183 -110
- package/src/utils.ts +144 -158
- package/dist/index.mjs +0 -1088
- package/dist/index.mjs.map +0 -1
- package/src/oslocate.js +0 -148
package/src/finder.ts
CHANGED
@@ -1,142 +1,142 @@
|
|
1
|
-
import { getPackageJson } from "flex-tools/package/getPackageJson"
|
2
|
-
import { getPackageRootPath } from 'flex-tools/package/getPackageRootPath';
|
3
|
-
import type { MixCli } from './cli';
|
4
|
-
import { globSync } from 'glob'
|
5
|
-
import { MixCliCommand } from './cli';
|
6
|
-
import { importModule,
|
7
|
-
import fs from "node:fs"
|
8
|
-
import path from "node:path"
|
9
|
-
|
10
|
-
|
11
|
-
/**
|
12
|
-
*
|
13
|
-
* 在当前工程中查找符合FlexCli.prefix约定的命令
|
14
|
-
*
|
15
|
-
* - 读取当前包的package.json
|
16
|
-
* - 找出所有以cli.prefix开头的依赖
|
17
|
-
* - 加载这些依赖的目录下的匹配cli.pattern的命令
|
18
|
-
* - 加载加载这样命令
|
19
|
-
*
|
20
|
-
*/
|
21
|
-
|
22
|
-
|
23
|
-
export function getMatchedDependencies(this:MixCli,entry:string):string[]{
|
24
|
-
const pacakgeMacher = this.options.include
|
25
|
-
if(!(pacakgeMacher instanceof RegExp)) return []
|
26
|
-
|
27
|
-
// 找出当前包的所有依赖
|
28
|
-
const { dependencies={},devDependencies={},peerDependencies={},optionalDependencies={},bundleDependencies={} } = getPackageJson(entry)
|
29
|
-
const packageNames = [
|
30
|
-
...Object.keys(dependencies),
|
31
|
-
...Object.keys(devDependencies),
|
32
|
-
...Object.keys(peerDependencies),
|
33
|
-
...Object.keys(optionalDependencies),
|
34
|
-
...Object.keys(bundleDependencies)
|
35
|
-
]
|
36
|
-
return packageNames.filter(name=>name!=="@voerka/cli" && pacakgeMacher.test(name))
|
37
|
-
}
|
38
|
-
|
39
|
-
function isMatched(str:string,reg?:string | RegExp | string[] | RegExp[]):boolean{
|
40
|
-
// let regexps:RegExp[]=[]
|
41
|
-
const regexps = reg ? (Array.isArray(reg) ? reg : [reg]) : []
|
42
|
-
return regexps.some(regexp=>{
|
43
|
-
if(typeof regexp === "string"){
|
44
|
-
return (new RegExp(regexp)).test(str)
|
45
|
-
}else if(regexp instanceof RegExp){
|
46
|
-
return regexp.test(str)
|
47
|
-
}else{
|
48
|
-
return false
|
49
|
-
}
|
50
|
-
})
|
51
|
-
}
|
52
|
-
|
53
|
-
export function findCliPaths(this:MixCli,packageName?:string ,entry?:string):string[]{
|
54
|
-
const includeMacher = this.options.include
|
55
|
-
const excludeMacher = this.options.exclude
|
56
|
-
if(!includeMacher) return []
|
57
|
-
const packageRoot = getPackageRootPath(entry || process.cwd())
|
58
|
-
const packagePath = packageName ? path.dirname(require.resolve(packageName,{paths:[packageRoot as string]})) : packageRoot
|
59
|
-
|
60
|
-
if(!packagePath) {
|
61
|
-
outputDebug("MixCli只能运行在Nodejs环境" )
|
62
|
-
return []
|
63
|
-
}
|
64
|
-
|
65
|
-
// 找出当前包的所有依赖
|
66
|
-
const packageNames = getMatchedDependencies.call(this,packagePath)
|
67
|
-
|
68
|
-
const cliDirs:string[]=[]
|
69
|
-
|
70
|
-
if(entry!==undefined) cliDirs.push(path.join(packagePath,this.options.cliDir))
|
71
|
-
packageNames.filter(name=>{
|
72
|
-
return isMatched(name,includeMacher) && !isMatched(name,excludeMacher)
|
73
|
-
})
|
74
|
-
.forEach(name=>{
|
75
|
-
outputDebug("匹配包:{}",`${packageName ? name+" <- "+packageName : name}`)
|
76
|
-
try{
|
77
|
-
const packageEntry = path.dirname(require.resolve(name,{paths:packagePath ? [packagePath] : [process.cwd()]}))
|
78
|
-
const packageCliDir =path.join(packageEntry,this.options.cliDir!)
|
79
|
-
// 查找当前包的所属工程的依赖
|
80
|
-
let dependencies = getMatchedDependencies.call(this,packageEntry)
|
81
|
-
cliDirs.push(...dependencies.reduce<string[]>((result,dependencie)=>{
|
82
|
-
outputDebug("匹配包:{}",`${dependencie} <- ${name}`)
|
83
|
-
result.push(...findCliPaths.call(this,dependencie,packageEntry))
|
84
|
-
return result
|
85
|
-
},[]))
|
86
|
-
if(fs.existsSync(packageCliDir)){
|
87
|
-
cliDirs.push(packageCliDir)
|
88
|
-
}
|
89
|
-
}catch(e:any){
|
90
|
-
outputDebug("解析包<{}>路径出错:{}",[name,e.stack])
|
91
|
-
}
|
92
|
-
})
|
93
|
-
// 由于一些包可能存在循环依赖,所以需要去重
|
94
|
-
return [...new Set(cliDirs)]
|
95
|
-
}
|
96
|
-
|
97
|
-
|
98
|
-
/**
|
99
|
-
*
|
100
|
-
* 扫描当前工程中所有符合条件的命令
|
101
|
-
*
|
102
|
-
* @param cli
|
103
|
-
*
|
104
|
-
*/
|
105
|
-
export async function findCommands(cli:MixCli){
|
106
|
-
const cliDirs = findCliPaths.call(cli)
|
107
|
-
const commands:MixCliCommand[] = []
|
108
|
-
const files = [] as string[]
|
109
|
-
cliDirs.forEach(dir=>{
|
110
|
-
globSync("*",{
|
111
|
-
cwd:dir,
|
112
|
-
absolute :true
|
113
|
-
}).forEach((file:string)=>{
|
114
|
-
const baseName = path.basename(file)
|
115
|
-
if(baseName.startsWith("_")) return
|
116
|
-
const ext = path.extname(file).toLowerCase()
|
117
|
-
if([".js",".cjs",".mjs"].includes(ext)){
|
118
|
-
files.push(file)
|
119
|
-
}else if(fs.statSync(file).isDirectory()){
|
120
|
-
files.push(path.join(file,"index.js"))
|
121
|
-
files.push(path.join(file,"index.cjs"))
|
122
|
-
files.push(path.join(file,"index.mjs"))
|
123
|
-
}
|
124
|
-
})
|
125
|
-
})
|
126
|
-
for(let file of files){
|
127
|
-
if(!fs.existsSync(file)) continue
|
128
|
-
try{
|
129
|
-
outputDebug("导入命令:{}",file)
|
130
|
-
if(file.endsWith(".cjs") || file.endsWith(".js")){
|
131
|
-
commands.push(await importModule(file))
|
132
|
-
}else if(file.endsWith(".mjs")){
|
133
|
-
const cmd = await import(`file://${file}`)
|
134
|
-
commands.push(cmd.default)
|
135
|
-
}
|
136
|
-
}catch(e:any){
|
137
|
-
outputDebug(e)
|
138
|
-
}
|
139
|
-
}
|
140
|
-
return commands
|
141
|
-
}
|
142
|
-
|
1
|
+
import { getPackageJson } from "flex-tools/package/getPackageJson"
|
2
|
+
import { getPackageRootPath } from 'flex-tools/package/getPackageRootPath';
|
3
|
+
import type { MixCli } from './cli';
|
4
|
+
import { globSync } from 'glob'
|
5
|
+
import { MixCliCommand } from './cli';
|
6
|
+
import { importModule, outputDebug } from './utils';
|
7
|
+
import fs from "node:fs"
|
8
|
+
import path from "node:path"
|
9
|
+
|
10
|
+
|
11
|
+
/**
|
12
|
+
*
|
13
|
+
* 在当前工程中查找符合FlexCli.prefix约定的命令
|
14
|
+
*
|
15
|
+
* - 读取当前包的package.json
|
16
|
+
* - 找出所有以cli.prefix开头的依赖
|
17
|
+
* - 加载这些依赖的目录下的匹配cli.pattern的命令
|
18
|
+
* - 加载加载这样命令
|
19
|
+
*
|
20
|
+
*/
|
21
|
+
|
22
|
+
|
23
|
+
export function getMatchedDependencies(this:MixCli,entry:string):string[]{
|
24
|
+
const pacakgeMacher = this.options.include
|
25
|
+
if(!(pacakgeMacher instanceof RegExp)) return []
|
26
|
+
|
27
|
+
// 找出当前包的所有依赖
|
28
|
+
const { dependencies={},devDependencies={},peerDependencies={},optionalDependencies={},bundleDependencies={} } = getPackageJson(entry)
|
29
|
+
const packageNames = [
|
30
|
+
...Object.keys(dependencies),
|
31
|
+
...Object.keys(devDependencies),
|
32
|
+
...Object.keys(peerDependencies),
|
33
|
+
...Object.keys(optionalDependencies),
|
34
|
+
...Object.keys(bundleDependencies)
|
35
|
+
]
|
36
|
+
return packageNames.filter(name=>name!=="@voerka/cli" && pacakgeMacher.test(name))
|
37
|
+
}
|
38
|
+
|
39
|
+
function isMatched(str:string,reg?:string | RegExp | string[] | RegExp[]):boolean{
|
40
|
+
// let regexps:RegExp[]=[]
|
41
|
+
const regexps = reg ? (Array.isArray(reg) ? reg : [reg]) : []
|
42
|
+
return regexps.some(regexp=>{
|
43
|
+
if(typeof regexp === "string"){
|
44
|
+
return (new RegExp(regexp)).test(str)
|
45
|
+
}else if(regexp instanceof RegExp){
|
46
|
+
return regexp.test(str)
|
47
|
+
}else{
|
48
|
+
return false
|
49
|
+
}
|
50
|
+
})
|
51
|
+
}
|
52
|
+
|
53
|
+
export function findCliPaths(this:MixCli,packageName?:string ,entry?:string):string[]{
|
54
|
+
const includeMacher = this.options.include
|
55
|
+
const excludeMacher = this.options.exclude
|
56
|
+
if(!includeMacher) return []
|
57
|
+
const packageRoot = getPackageRootPath(entry || process.cwd())
|
58
|
+
const packagePath = packageName ? path.dirname(require.resolve(packageName,{paths:[packageRoot as string]})) : packageRoot
|
59
|
+
|
60
|
+
if(!packagePath) {
|
61
|
+
outputDebug("MixCli只能运行在Nodejs环境" )
|
62
|
+
return []
|
63
|
+
}
|
64
|
+
|
65
|
+
// 找出当前包的所有依赖
|
66
|
+
const packageNames = getMatchedDependencies.call(this,packagePath)
|
67
|
+
|
68
|
+
const cliDirs:string[]=[]
|
69
|
+
|
70
|
+
if(entry!==undefined) cliDirs.push(path.join(packagePath,this.options.cliDir))
|
71
|
+
packageNames.filter(name=>{
|
72
|
+
return isMatched(name,includeMacher) && !isMatched(name,excludeMacher)
|
73
|
+
})
|
74
|
+
.forEach(name=>{
|
75
|
+
outputDebug("匹配包:{}",`${packageName ? name+" <- "+packageName : name}`)
|
76
|
+
try{
|
77
|
+
const packageEntry = path.dirname(require.resolve(name,{paths:packagePath ? [packagePath] : [process.cwd()]}))
|
78
|
+
const packageCliDir =path.join(packageEntry,this.options.cliDir!)
|
79
|
+
// 查找当前包的所属工程的依赖
|
80
|
+
let dependencies = getMatchedDependencies.call(this,packageEntry)
|
81
|
+
cliDirs.push(...dependencies.reduce<string[]>((result,dependencie)=>{
|
82
|
+
outputDebug("匹配包:{}",`${dependencie} <- ${name}`)
|
83
|
+
result.push(...findCliPaths.call(this,dependencie,packageEntry))
|
84
|
+
return result
|
85
|
+
},[]))
|
86
|
+
if(fs.existsSync(packageCliDir)){
|
87
|
+
cliDirs.push(packageCliDir)
|
88
|
+
}
|
89
|
+
}catch(e:any){
|
90
|
+
outputDebug("解析包<{}>路径出错:{}",[name,e.stack])
|
91
|
+
}
|
92
|
+
})
|
93
|
+
// 由于一些包可能存在循环依赖,所以需要去重
|
94
|
+
return [...new Set(cliDirs)]
|
95
|
+
}
|
96
|
+
|
97
|
+
|
98
|
+
/**
|
99
|
+
*
|
100
|
+
* 扫描当前工程中所有符合条件的命令
|
101
|
+
*
|
102
|
+
* @param cli
|
103
|
+
*
|
104
|
+
*/
|
105
|
+
export async function findCommands(cli:MixCli){
|
106
|
+
const cliDirs = findCliPaths.call(cli)
|
107
|
+
const commands:MixCliCommand[] = []
|
108
|
+
const files = [] as string[]
|
109
|
+
cliDirs.forEach(dir=>{
|
110
|
+
globSync("*",{
|
111
|
+
cwd:dir,
|
112
|
+
absolute :true
|
113
|
+
}).forEach((file:string)=>{
|
114
|
+
const baseName = path.basename(file)
|
115
|
+
if(baseName.startsWith("_")) return
|
116
|
+
const ext = path.extname(file).toLowerCase()
|
117
|
+
if([".js",".cjs",".mjs"].includes(ext)){
|
118
|
+
files.push(file)
|
119
|
+
}else if(fs.statSync(file).isDirectory()){
|
120
|
+
files.push(path.join(file,"index.js"))
|
121
|
+
files.push(path.join(file,"index.cjs"))
|
122
|
+
files.push(path.join(file,"index.mjs"))
|
123
|
+
}
|
124
|
+
})
|
125
|
+
})
|
126
|
+
for(let file of files){
|
127
|
+
if(!fs.existsSync(file)) continue
|
128
|
+
try{
|
129
|
+
outputDebug("导入命令:{}",file)
|
130
|
+
if(file.endsWith(".cjs") || file.endsWith(".js")){
|
131
|
+
commands.push(await importModule(file))
|
132
|
+
}else if(file.endsWith(".mjs")){
|
133
|
+
const cmd = await import(`file://${file}`)
|
134
|
+
commands.push(cmd.default)
|
135
|
+
}
|
136
|
+
}catch(e:any){
|
137
|
+
outputDebug(e)
|
138
|
+
}
|
139
|
+
}
|
140
|
+
return commands
|
141
|
+
}
|
142
|
+
|
package/src/index.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
export * from "./cli"
|
2
|
-
export * from "./utils"
|
3
|
-
export * from "./command"
|
4
|
-
export * from "./option"
|
1
|
+
export * from "./cli"
|
2
|
+
export * from "./utils"
|
3
|
+
export * from "./command"
|
4
|
+
export * from "./option"
|
5
|
+
export * from "./prompt"
|
package/src/option.ts
CHANGED
@@ -1,99 +1,57 @@
|
|
1
|
-
import { Option } from 'commander'
|
1
|
+
import { Option, OptionValues } from 'commander'
|
2
2
|
import { PromptObject } from 'prompts'
|
3
|
-
import {
|
3
|
+
import { PromptChoice, MixOptionPrompt,PromptParams } from './prompt'
|
4
|
+
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
export interface MixedOptionParams {
|
7
|
+
required? : boolean; // A value must be supplied when the option is specified.
|
8
|
+
optional? : boolean; // A value is optional when the option is specified.
|
9
|
+
variadic? : boolean;
|
10
|
+
mandatory? : boolean; // The option must have a value after parsing, which usually means it must be specified on command line.
|
11
|
+
negate? : boolean;
|
12
|
+
default? : any;
|
13
|
+
defaultDescription? : string;
|
14
|
+
conflicts? : string | string[];
|
15
|
+
argParser? : unknown;
|
16
|
+
implies? : OptionValues
|
17
|
+
envVar? : string;
|
18
|
+
parseArg? : <T>(value: string, previous: T) => T;
|
19
|
+
hidden? : boolean;
|
20
|
+
choices? : (string | PromptChoice )[] | ((pre:any,answers:any)=>(string | PromptChoice)[]);
|
21
|
+
validate? : (value: any) => boolean;
|
22
|
+
preset? : unknown;
|
23
|
+
prompt? : PromptParams
|
15
24
|
}
|
16
25
|
|
17
26
|
|
18
|
-
export class MixOption extends Option
|
27
|
+
export class MixOption extends Option{
|
19
28
|
__MIX_OPTION__ = true
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
if(arguments.length==3 && typeof arguments[2] == "object"){
|
28
|
-
params = Object.assign({ },arguments[2])
|
29
|
-
}else if(arguments.length==3){
|
30
|
-
params.default = arguments[2]
|
31
|
-
}
|
32
|
-
if(params.prompt===undefined) params.prompt = 'auto'
|
29
|
+
prompt? : MixOptionPrompt
|
30
|
+
constructor(flags: string, description: string,public params?: MixedOptionParams) {
|
31
|
+
super(flags, description)
|
32
|
+
this._setOption(params || {})
|
33
|
+
this.prompt = new MixOptionPrompt(this,params?.prompt)
|
34
|
+
}
|
35
|
+
private _setOption(params:MixedOptionParams){
|
33
36
|
if(params.default) this.default(params.default,params.defaultDescription)
|
34
|
-
if(params.choices) this.choices(params.choices)
|
35
37
|
if(params.conflicts) this.conflicts(params.conflicts)
|
36
|
-
if(params.
|
37
|
-
if(params.
|
38
|
-
if(params.hideHelp) this.hideHelp(params.hideHelp)
|
38
|
+
if(params.envVar) this.env(params.envVar)
|
39
|
+
if(params.parseArg) this.argParser(params.parseArg)
|
39
40
|
if(params.hidden) this.hidden = params.hidden
|
40
41
|
if(params.mandatory) this.makeOptionMandatory(params.mandatory)
|
41
42
|
if(params.implies) this.implies(params.implies)
|
42
43
|
if(params.optional) this.optional=params.optional
|
43
|
-
if(
|
44
|
-
if(params.
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
validate(value: any): boolean {
|
51
|
-
if(typeof(this._validate)=='function'){
|
52
|
-
return this._validate(value)
|
53
|
-
}else{
|
54
|
-
return true
|
55
|
-
}
|
56
|
-
}
|
57
|
-
// @ts-ignore
|
58
|
-
choices(values:(PromptChoice | string)[]){
|
59
|
-
if(!this.promptChoices){
|
60
|
-
this.promptChoices = values.map(choice=>{
|
61
|
-
if(typeof(choice)=='object'){
|
62
|
-
return choice
|
63
|
-
}else{
|
64
|
-
return {title:choice,value:choice}
|
65
|
-
}
|
66
|
-
})
|
67
|
-
}
|
68
|
-
super.choices(this.promptChoices.map((item:any)=>item.value))
|
69
|
-
}
|
70
|
-
|
71
|
-
private resetChoices(){
|
72
|
-
super.choices(this.promptChoices!.map((item:any)=>item.value))
|
73
|
-
}
|
74
|
-
|
75
|
-
addChoice(value:PromptChoice | string){
|
76
|
-
if(!this.promptChoices || !Array.isArray(this.promptChoices)) this.promptChoices = []
|
77
|
-
this.promptChoices!.push(typeof(value)=='string' ? {title:value,value} : value)
|
78
|
-
this.resetChoices()
|
79
|
-
}
|
80
|
-
removeChoice(value:any){
|
81
|
-
this.promptChoices =this.promptChoices?.filter(choice=>choice.value!==value)
|
82
|
-
this.resetChoices()
|
83
|
-
}
|
84
|
-
clearChoice(){
|
85
|
-
this.promptChoices = []
|
86
|
-
this.resetChoices()
|
87
|
-
}
|
88
|
-
|
89
|
-
|
44
|
+
if(params.variadic) this.variadic = params.variadic
|
45
|
+
if(params.negate) this.negate = params.negate
|
46
|
+
if(params.preset) this.preset(params.preset)
|
47
|
+
if(params.required) this.required = params.required
|
48
|
+
if(Array.isArray(params.choices)) this.choices(params.choices.map(choice=>typeof(choice)=='string' ? choice : choice.value))
|
49
|
+
}
|
50
|
+
|
90
51
|
/**
|
91
52
|
* 返回选项的提示对象
|
92
53
|
*
|
93
|
-
* @remarks
|
94
|
-
*
|
95
|
-
*
|
96
|
-
*
|
54
|
+
* @remarks
|
97
55
|
* @param inputValue
|
98
56
|
* @returns
|
99
57
|
*/
|