mixcli 3.0.10 → 3.2.1
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.
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -0
- package/dist/{index.d.mts → index.d.cts} +75 -99
- package/dist/index.d.ts +75 -99
- package/dist/index.js +3 -1125
- package/dist/index.js.map +1 -1
- package/package.json +30 -13
- package/readme.md +8 -12
- package/src/cli.ts +21 -21
- package/src/command.ts +452 -451
- package/src/finder.ts +142 -142
- package/src/index.ts +5 -4
- package/src/option.ts +40 -81
- package/src/prompt.ts +183 -110
- package/src/utils.ts +144 -158
- package/dist/index.mjs +0 -1081
- 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,98 +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
|
19
|
-
|
20
|
-
prompt
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
params = Object.assign({ },arguments[2])
|
28
|
-
}else if(arguments.length==3){
|
29
|
-
params.default = arguments[2]
|
30
|
-
}
|
31
|
-
if(params.prompt===undefined) params.prompt = 'auto'
|
27
|
+
export class MixOption extends Option{
|
28
|
+
__MIX_OPTION__ = true
|
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){
|
32
36
|
if(params.default) this.default(params.default,params.defaultDescription)
|
33
|
-
if(params.choices) this.choices(params.choices)
|
34
37
|
if(params.conflicts) this.conflicts(params.conflicts)
|
35
|
-
if(params.
|
36
|
-
if(params.
|
37
|
-
if(params.hideHelp) this.hideHelp(params.hideHelp)
|
38
|
+
if(params.envVar) this.env(params.envVar)
|
39
|
+
if(params.parseArg) this.argParser(params.parseArg)
|
38
40
|
if(params.hidden) this.hidden = params.hidden
|
39
41
|
if(params.mandatory) this.makeOptionMandatory(params.mandatory)
|
40
42
|
if(params.implies) this.implies(params.implies)
|
41
43
|
if(params.optional) this.optional=params.optional
|
42
|
-
if(
|
43
|
-
if(params.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
validate(value: any): boolean {
|
50
|
-
if(typeof(this._validate)=='function'){
|
51
|
-
return this._validate(value)
|
52
|
-
}else{
|
53
|
-
return true
|
54
|
-
}
|
55
|
-
}
|
56
|
-
// @ts-ignore
|
57
|
-
choices(values:(PromptChoice | string)[]){
|
58
|
-
if(!this.promptChoices){
|
59
|
-
this.promptChoices = values.map(choice=>{
|
60
|
-
if(typeof(choice)=='object'){
|
61
|
-
return choice
|
62
|
-
}else{
|
63
|
-
return {title:choice,value:choice}
|
64
|
-
}
|
65
|
-
})
|
66
|
-
}
|
67
|
-
super.choices(this.promptChoices.map((item:any)=>item.value))
|
68
|
-
}
|
69
|
-
|
70
|
-
private resetChoices(){
|
71
|
-
super.choices(this.promptChoices!.map((item:any)=>item.value))
|
72
|
-
}
|
73
|
-
|
74
|
-
addChoice(value:PromptChoice | string){
|
75
|
-
if(!this.promptChoices || !Array.isArray(this.promptChoices)) this.promptChoices = []
|
76
|
-
this.promptChoices!.push(typeof(value)=='string' ? {title:value,value} : value)
|
77
|
-
this.resetChoices()
|
78
|
-
}
|
79
|
-
removeChoice(value:any){
|
80
|
-
this.promptChoices =this.promptChoices?.filter(choice=>choice.value!==value)
|
81
|
-
this.resetChoices()
|
82
|
-
}
|
83
|
-
clearChoice(){
|
84
|
-
this.promptChoices = []
|
85
|
-
this.resetChoices()
|
86
|
-
}
|
87
|
-
|
88
|
-
|
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
|
+
|
89
51
|
/**
|
90
52
|
* 返回选项的提示对象
|
91
53
|
*
|
92
|
-
* @remarks
|
93
|
-
*
|
94
|
-
*
|
95
|
-
*
|
54
|
+
* @remarks
|
96
55
|
* @param inputValue
|
97
56
|
* @returns
|
98
57
|
*/
|