mixcli 3.2.0 → 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/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
  */