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/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,99 +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{
27
+ export class MixOption extends Option{
19
28
  __MIX_OPTION__ = true
20
- // 是否提示用户输入
21
- prompt?: PromptManager
22
- promptChoices?:PromptChoice[]
23
- private _validate?: (value: any) => boolean
24
- constructor(flags: string, description?: string | undefined,optsOrDefault?:any) {
25
- super(flags, description)
26
- let params:MixedOptionParams = {}
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.env) this.env(params.env)
37
- if(params.argParser) this.argParser(params.argParser)
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(typeof(params.validate)=='function') this._validate = params.validate.bind(this)
44
- if(params.required) {
45
- this.required = params.required
46
- if(!this._validate ) this._validate = (value:any)=>String(value).length>0
47
- }
48
- this.prompt = new PromptManager(this as IPromptable,params.prompt)
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
  */