mixcli 3.0.10 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
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, isDebug, 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
-
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 { IPromptable, IPromptableOptions, PromptChoice, PromptManager } from './prompt'
3
+ import { PromptChoice, MixOptionPrompt,PromptParams } from './prompt'
4
+
4
5
 
5
-
6
- export interface MixedOptionParams extends IPromptableOptions{
7
- hidden?:boolean
8
- defaultDescription?:string // 默认值的描述
9
- conflicts?:string | string[]
10
- env?:string
11
- argParser?:<T>(value: string, previous: T) => T
12
- hideHelp?:boolean
13
- mandatory?: boolean
14
- implies?:{[key:string]:any}
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 implements IPromptable{
19
- // 是否提示用户输入
20
- prompt?: PromptManager
21
- promptChoices?:PromptChoice[]
22
- private _validate?: (value: any) => boolean
23
- constructor(flags: string, description?: string | undefined,optsOrDefault?:any) {
24
- super(flags, description)
25
- let params:MixedOptionParams = {}
26
- if(arguments.length==3 && typeof arguments[2] == "object"){
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.env) this.env(params.env)
36
- if(params.argParser) this.argParser(params.argParser)
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(typeof(params.validate)=='function') this._validate = params.validate.bind(this)
43
- if(params.required) {
44
- this.required = params.required
45
- if(!this._validate ) this._validate = (value:any)=>String(value).length>0
46
- }
47
- this.prompt = new PromptManager(this as IPromptable,params.prompt)
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
  */