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/prompt.ts CHANGED
@@ -1,113 +1,190 @@
1
+ import { isPlainObject } from "flex-tools/typecheck/isPlainObject"
2
+ import { isNumber } from "flex-tools/typecheck/isNumber"
3
+
1
4
  import { PromptObject } from "prompts"
2
5
  import { outputDebug } from "./utils"
3
-
6
+ import { MixOption } from "./option"
4
7
 
5
- export type PromptType = "text" | "password" | "invisible" | "number"| "confirm"| "list"| "toggle"| "select" | "multiselect" | "autocomplete" | "date" | "autocompleteMultiselect"
8
+ export type PromptType = "text" | "password" | "invisible" | "number"| "confirm"| "list"
9
+ | "toggle"| "select" | "multiselect" | "autocomplete" | "date" | "autocompleteMultiselect"
6
10
 
7
11
  export type PromptParam = 'auto' | boolean | PromptType | PromptObject
8
12
  export type InputPromptParam = PromptParam | ((value:any)=>PromptParam) | boolean
9
13
  export type PromptParamDefaultValue = string | boolean | string[]
10
14
 
11
15
  export const promptTypeMap:Record<string,string> = {
12
- boolean:"confirm",
13
- string:"text",
14
- number:"number",
15
- array:"list",
16
+ boolean: "confirm",
17
+ string : "text",
18
+ number : "number",
19
+ array : "list",
20
+ date : "date"
16
21
  }
17
22
 
18
- export const supportedPromptTypes = ["text","password","invisible", "number", "confirm" , "list", "toggle" , "select" , "multiselect" , "autocomplete" , "date" , "autocompleteMultiselect"]
23
+ export const supportedPromptTypes = [
24
+ "text",
25
+ "password",
26
+ "invisible",
27
+ "number",
28
+ "confirm" ,
29
+ "list",
30
+ "toggle" ,
31
+ "select" ,
32
+ "multiselect" ,
33
+ "autocomplete" ,
34
+ "date" ,
35
+ "autocompleteMultiselect"
36
+ ]
37
+
19
38
  export interface PromptChoice {
20
- title: string;
21
- value?: any;
22
- disabled?: boolean | undefined;
23
- selected?: boolean | undefined;
39
+ title : string;
40
+ value? : any;
41
+ disabled? : boolean | undefined;
42
+ selected? : boolean | undefined;
24
43
  description?: string | undefined;
25
44
  }
26
45
 
27
-
28
-
29
- export interface IPromptableOptions{
30
- required?: boolean; // A value must be supplied when the option is specified.
31
- optional?: boolean; // A value is optional when the option is specified.
32
- default?:PromptParamDefaultValue
33
- choices?:(PromptChoice | any)[] // 选项值的可选值
34
- prompt?:InputPromptParam
35
- validate?:(value: any) => boolean
36
- }
37
-
38
-
39
- export interface IPromptable{
40
- name():string
41
- description?:string
42
- flags:string
43
- promptChoices?:PromptChoice[]
44
- argChoices?:string[]
45
- variadic?:boolean
46
- defaultValue?:PromptParamDefaultValue
47
- input?:any
48
- required?:boolean
49
- validate?: (value: any) => boolean
50
- getPrompt(inputValue?:any):PromptObject | undefined
51
- }
52
-
53
- /**
54
- * 供command.option()使用的参数对象
55
- */
56
- export interface PromptableObject{
57
-
58
-
59
- }
60
-
46
+
47
+ export type PromptParams = Omit<PromptObject,'name'> | PromptType | boolean | 'auto' | undefined
61
48
 
62
49
  /**
63
50
  * 负责生成prompt对象
64
51
  *
65
52
  */
66
- export class PromptManager{
67
- args:InputPromptParam
68
- private _promptable:IPromptable // 对应的FlexOption或FlexArgument
69
- constructor(promptable:IPromptable,promptArgs?:InputPromptParam){
70
- this._promptable = promptable
71
- this.args= promptArgs===undefined ? 'auto' : promptArgs
53
+ export class MixOptionPrompt{
54
+ params?: PromptParams
55
+ constructor(public cliOption:MixOption,promptParams?:PromptParams){
56
+ this.params = promptParams
72
57
  }
73
-
74
58
  /**
75
59
  * 返回输入的是否是有效的prompt类型
76
60
  * @param type
77
61
  * @returns
78
62
  */
79
- isValid(type:any){
63
+ isValidPromptType(type:any){
80
64
  return supportedPromptTypes.includes(String(type))
81
65
  }
82
66
  /**
83
67
  * 推断是否需要提示
84
68
  *
69
+ * 1. 显式指定prompt=true或者提示类型,或者提示对象,则需要提示
70
+ *
71
+ *
85
72
  */
86
- isNeed(input:any,defaultValue?:any){
73
+ isNeedPrompt(input:any,defaultValue?:any){
87
74
 
88
- const promptArg = this.args
75
+ const params = this.params
76
+
89
77
  const inputValue = input || defaultValue
78
+
90
79
  // 是否有输入值,即在命令行输入了值
91
80
  const hasInput = !(inputValue === undefined)
81
+
92
82
  // 1. 显式指定了_prompt为true,则需要提示,后续进行提示类型的推断,可能不会准确
93
- if(promptArg===true) return true
94
- if(promptArg===false) return false
83
+ if(params === true) return true
84
+ if(params === false) return false
85
+ if(params === 'auto') return !hasInput
95
86
 
96
87
  // 2. 提供了一个prompt对象,并且没有在命令行输入值,则需要提示
97
- if(typeof(promptArg)=='object'){
88
+ if(isPlainObject(params)){
98
89
  return !hasInput
99
90
  }
100
91
 
101
92
  // 3. 指定了内置的prompt类型,如prompt='password',则使用password类型提示输入
102
- if(typeof(promptArg) == 'string' && supportedPromptTypes.includes(promptArg)){
103
- return !hasInput
93
+ if(typeof(params) == 'string' && this.isValidPromptType(params)){
94
+ return true
104
95
  }
96
+
97
+ // 4. 指定了可选值,但是没有输入值,则需要提示
98
+ const isOptional = /(\<s*\w\s*\>)/.test(this.cliOption.flags)
99
+ if(isOptional) return !hasInput
105
100
 
106
101
  // 4. 判断输入是否有效,则显示提示
107
- if(this._promptable.argChoices && this._promptable.argChoices.indexOf(inputValue) == -1){
102
+ if(this.cliOption.argChoices && this.cliOption.argChoices.indexOf(inputValue) == -1){
108
103
  return true
109
104
  }
110
105
  return !hasInput
106
+
107
+ }
108
+
109
+ private _getChoices(){
110
+ let choices:(string | PromptChoice)[] | ((pre:any,answers:any)=>(string | PromptChoice)[]) = []
111
+ let choicesParam = this.cliOption.params?.choices
112
+ if(this.cliOption.argChoices){
113
+ choices = this.cliOption.argChoices.map(choice=>{
114
+ if(typeof(choice)=='string'){
115
+ return {title:choice,value:choice}
116
+ }else{
117
+ return choice
118
+ }
119
+ })
120
+ }else if(choicesParam){
121
+ choices = typeof(choicesParam)=='function' ? choicesParam : []
122
+ }else{
123
+ return []
124
+ }
125
+ return choices
126
+ }
127
+
128
+ /**
129
+ * 自动推断prompt类型
130
+ *
131
+ *
132
+ *
133
+ * @param inputValue 从命令行输入的值
134
+ */
135
+ infer(inputValue?:any){
136
+
137
+ const { variadic, defaultValue } = this.cliOption
138
+
139
+ const input = inputValue || defaultValue
140
+
141
+ // 如果选择指定了"-p [value]或[value...]",则使用text类型,如果没有要求输入值,则使用confirm类型
142
+ let promptType:PromptType = 'text'
143
+
144
+ const params = this.params
145
+
146
+ if(this.isValidPromptType(params)){ // 显式指定了prompt类型,m则以指定的类型为准
147
+ promptType = params as PromptType
148
+ }else if(isPlainObject(params)){ // 显式指定了prompt对象
149
+ promptType = (params as PromptObject).type as PromptType
150
+ }else{ // 自动推断prompt类型
151
+
152
+ const isListType = /(\[\s*\w+\.\.\.\s*])|(\<\s*\w+\.\.\.\s*>)/.test(this.cliOption.flags)
153
+ const isTextType = /(\<s*\w+\s*\>)|(\[\w+\])/.test(this.cliOption.flags)
154
+ const isBooleanType = !/(\[\s*\w+s*])|(\<\s*\w+\s*>)/.test(this.cliOption.flags)
155
+ const isNumberType = isNumber(defaultValue)
156
+ const isDate = defaultValue && defaultValue instanceof Date
157
+
158
+ // 根据默认值的类型推断
159
+ const datatype:string = Array.isArray(input) ? 'array' :
160
+ input instanceof Date ? 'date' :
161
+ typeof(input)
162
+
163
+ const optionParams = this.cliOption.params
164
+
165
+ if(optionParams && optionParams.choices){
166
+ const choices = optionParams.choices
167
+ if(isBooleanType && (Array.isArray(choices) && choices.length==2)){
168
+ promptType = 'toggle'
169
+ }else{
170
+ promptType = variadic ? 'multiselect' : 'select'
171
+ }
172
+ }else if(isListType){ // 提供多个可选值时
173
+ promptType = 'list'
174
+ }else if(isDate){
175
+ promptType = 'date'
176
+ }else if(isNumberType){
177
+ promptType = 'number'
178
+ }else if(isTextType){ // 提供一个可选值时
179
+ promptType = 'text'
180
+ }else if(isBooleanType || typeof(defaultValue)==='boolean'){
181
+ promptType = 'confirm'
182
+ }else if(datatype in promptTypeMap){
183
+ promptType = promptTypeMap[datatype] as PromptType
184
+ }
185
+ }
186
+ outputDebug("选项<{}> -> 提示类型<{}>",[this.cliOption.name(),promptType])
187
+ return promptType
111
188
  }
112
189
  /**
113
190
  * 返回生成prompt对象
@@ -115,66 +192,62 @@ export class PromptManager{
115
192
  * @param inputValue 从命令行输入的值
116
193
  */
117
194
  get(inputValue?:any){
118
- const {description,promptChoices,validate,defaultValue} = this._promptable
195
+
196
+ const { description, defaultValue } = this.cliOption
197
+
119
198
  let input = inputValue || defaultValue
120
- // 判断是否需要输入提示
121
- if(!this.isNeed(input,defaultValue)) return
122
- // 推断prompt类型
123
- let promptType = this.infer(inputValue)
199
+
200
+ // 1. 判断是否需要启用提示
201
+ if(!this.isNeedPrompt(input,defaultValue)) return
202
+
203
+ // 2. 推断prompt类型
204
+ const promptType = this.infer(inputValue)
205
+
124
206
  const prompt = {
125
- type:promptType,
126
- name:this._promptable.name(),
127
- message:description,
207
+ type : promptType,
208
+ name : this.cliOption.attributeName(),
209
+ message: description,
128
210
  initial: input,
129
- ...typeof(this.args) == 'object' ? this.args : {}
211
+ ...typeof(this.params) == 'object' ? this.params : {}
130
212
  } as PromptObject
213
+
214
+
131
215
  // 指定了验证函数,用来验证输入值是否有效
132
- prompt.validate = validate?.bind(this._promptable)
216
+ prompt.validate = this.cliOption.params?.validate
217
+
133
218
  if(promptType=='multiselect') prompt.instructions=false
134
- if(['select','multiselect'].includes(promptType)){
135
- let index = promptChoices?.findIndex(item=>item.value==input)
136
- prompt.initial = index==-1 ? undefined : index
137
- }
138
- // 选项值的可选值
139
- if(Array.isArray(promptChoices)) {
140
- prompt.choices =promptChoices
219
+ prompt.choices = prompt.choices || this._getChoices() as any
220
+
221
+ if(['select','multiselect'].includes(promptType)){
222
+
223
+ }else if(promptType=='toggle'){
224
+ if(Array.isArray(prompt.choices)){
225
+ if(!prompt.active) prompt.active = prompt.choices[0].value
226
+ if(!prompt.inactive) prompt.inactive = prompt.choices[1].value
227
+ }
141
228
  }
142
- return prompt
143
- }
144
- /**
145
- * 推断prompt类型
146
- *
147
- * @param inputValue 从命令行输入的值
148
- */
149
- infer(inputValue?:any){
150
- const {argChoices,variadic,defaultValue} = this._promptable
151
- let input = inputValue || defaultValue
152
- // 如果选择指定了"-p [value]或[value...]",则使用text类型,如果没有要求输入值,则使用confirm类型
153
- let promptType = /(\<[\w\.]+\>)|(\[[\w\.]+\])/.test(this._promptable.flags) ? 'text' : 'confirm'
154
- let promptArg = this.args
155
- if(this.isValid(promptArg)){ // 显式指定了prompt类型
156
- promptType = promptArg as string
157
- }else{ // 未显式指定prompt类型,需要按一定规则推断类型
158
- if(typeof(promptArg)=='object'){
159
- promptType = promptArg.type as string
160
- }else{
161
- if(argChoices){ // 提供多个可选值时
162
- promptType = variadic ? 'multiselect' : 'select'
163
- }else{
164
- const datatype:string = Array.isArray(defaultValue) ? 'array' : typeof(defaultValue)
165
- // 如果输入值班是数组,则使用list类型,允许使用逗号分隔的多个值
166
- if(Array.isArray(input) || variadic){
167
- promptType = "list"
168
- }else{
169
- if(datatype in promptTypeMap){
170
- promptType = promptTypeMap[datatype]
171
- }
229
+
230
+ if(input && typeof(prompt.initial)!='function'){
231
+ if(prompt.choices && Array.isArray(prompt.choices)){
232
+ if(promptType=='select'){
233
+ const index = Array.isArray(prompt.choices) ? prompt.choices.findIndex(item=>item.value==input) : -1
234
+ if(index!=-1){
235
+ prompt.initial = index
172
236
  }
237
+ }else if(promptType=='multiselect'){
238
+ prompt.choices.forEach((item)=>{
239
+ if(Array.isArray(input) && input.includes(item.value)){
240
+ item.selected = true
241
+ }else if(item.value==input){
242
+ item.selected = true
243
+ }
244
+ })
173
245
  }
174
246
  }
175
247
  }
176
- outputDebug("选项<{}> -> 提示类型<{}>",[this._promptable.name(),promptType])
177
- return promptType
248
+
249
+ return prompt
178
250
  }
179
251
 
252
+
180
253
  }
package/src/utils.ts CHANGED
@@ -1,158 +1,144 @@
1
- import artTemplate from "art-template"
2
- import fs from "fs-extra"
3
- import path from "node:path"
4
- import { promisify } from "flex-tools/func/promisify"
5
- import logsets from "logsets"
6
- /**
7
- *
8
- * 在控制台输出一个字符串
9
- * 本方法会将字符串中的每一行空格去掉
10
- *
11
- * @remarks
12
- *
13
- * outputStr(String.raw`
14
- * a
15
- * b`)
16
- *
17
- * 会输出
18
- * a
19
- * b
20
- *
21
- * 此功能可以用于输出多行字符串时,保持代码的缩进格式,而不会影响输出结果
22
- *
23
- * @param str : 要输出的字符串
24
- * @param vars : 用于替换字符串中的变量
25
- *
26
- */
27
- export function outputStr(str:string,vars?:Record<string,any> | any[]){
28
- logsets.log(fixIndent(str),vars)
29
- }
30
-
31
- /**
32
- * 修正多行字符串的缩进
33
- *
34
- * @param text
35
- * @param indent
36
- * @returns
37
- */
38
- export function fixIndent(text:string,indent?:boolean | number):string{
39
- let indentValue = (indent==undefined || indent===true) ? 0 : (typeof(indent)=='number' ? indent : -1)
40
- if(indentValue==-1) return text // 不修正缩进
41
- let lines:string[] = text.split("\n")
42
- let minSpaceCount = lines.reduce<number>((minCount,line,index)=>{
43
- if(index==0) return minCount
44
- const spaceCount = line.match(/^\s*/)?.[0].length || 0
45
- return Math.min(minCount,spaceCount)
46
- },9999)
47
- lines = lines.map(line=>line.substring(minSpaceCount))
48
- return lines.join("\n")
49
- }
50
-
51
- /**
52
- * 增加内置选项
53
- * @param command
54
- */
55
- export function addBuiltInOptions(command:any){
56
- command.option("--work-dirs <values...>","指定工作目录",{hidden:true,optional:true,required:true,prompt:false})
57
- command.option("--disable-prompts","禁用所有交互提示",{hidden:true,prompt:false})
58
- command.option("--debug-cli","显示调试信息",{hidden:true,prompt:false})
59
- }
60
-
61
-
62
- /**
63
- * 是否命令行中包含了--debug-cli选项
64
- */
65
- export function isDebug(){
66
- return process.argv.includes("--debug-cli")
67
- }
68
- export function isEnablePrompts(){
69
- return !process.argv.includes("--disable-prompts")
70
- }
71
-
72
- /**
73
- * 打印调试信息
74
- * @param message
75
- * @param args
76
- */
77
- export function outputDebug(message:string,...args:any[]){
78
- let vars = (args.length == 1 && typeof(args[0])=='function') ? args[0]() : args
79
- if(isDebug()) logsets.log(`[MixCli] ${message}`,...vars)
80
- }
81
-
82
- export const fileExists = promisify(fs.exists,{
83
- parseCallback:(results)=>{
84
- return results[0]
85
- }
86
- })
87
- export const readFile = promisify(fs.readFile)
88
- export const writeFile = promisify(fs.writeFile)
89
- export const mkdir = promisify(fs.mkdir)
90
-
91
- /**
92
- * 基于artTemplate模板生成文件
93
- *
94
- * @param {*} tmplFile
95
- * @param {*} vars
96
- */
97
- export async function createFileByTemplate(targetFile:string,tmplFile:string,vars:Record<string,any>={}){
98
- tmplFile=path.isAbsolute(tmplFile)? tmplFile : path.join(process.cwd(),tmplFile)
99
- if(!fs.existsSync(tmplFile)){
100
- throw new Error("模板文件不存在:"+tmplFile)
101
- }
102
- targetFile=path.isAbsolute(targetFile)? targetFile : path.join(process.cwd(),targetFile)
103
- const outPath = path.dirname(targetFile)
104
- if(!await fileExists(outPath)){
105
- await mkdir(outPath,{recursive:true})
106
- }
107
- const template = artTemplate(tmplFile,await readFile(tmplFile,{encoding:"utf-8"}));
108
- await writeFile(targetFile,template(vars),{encoding:"utf-8"})
109
- return targetFile
110
- }
111
-
112
- /**
113
- * 创建目录
114
- *
115
- *
116
- *
117
- * @param {String[]} dirs 要创建的目录列表,类型为字符串数组
118
- * @param callback 创建目录过程中的回调函数,类型为异步函数,接收一个参数 dir,表示当前正在创建的目录
119
- * @returns 该函数返回一个 Promise 对象,表示创建目录的操作是否完成
120
- */
121
- export async function mkDirs(dirs:string[],{callback,base}:{callback?:Function,base?:string}){
122
- if(!Array.isArray(dirs)) throw new Error("dirs参数必须为字符串数组")
123
- for(let dir of dirs){
124
- if(!path.isAbsolute(dir)) dir = path.join(base || process.cwd(),dir)
125
- if(typeof(callback)=='function') callback(dir)
126
- await mkdir(dir,{recursive:true})
127
- }
128
- }
129
-
130
- export function showError(e:any){
131
- if(isDebug()){
132
- outputDebug("导入命令<>出错:{}",e.stack)
133
- }else{
134
- console.error(e)
135
- }
136
-
137
- }
138
-
139
-
140
- export function getId(){
141
- return Math.random().toString(36).substr(2)
142
- }
143
-
144
-
145
- export async function importModule(file:string){
146
- let module
147
- try{
148
- module = require(file)
149
- }catch(e:any){
150
- try{
151
- const cmd = await import(`file://${file}`)
152
- module = cmd.default
153
- }catch(e:any){
154
- throw e
155
- }
156
- }
157
- return module
158
- }
1
+ import fs from "fs-extra"
2
+ import path from "node:path"
3
+ import { promisify } from "flex-tools/func/promisify"
4
+ import logsets from "logsets"
5
+
6
+
7
+ /**
8
+ *
9
+ * 在控制台输出一个字符串
10
+ * 本方法会将字符串中的每一行空格去掉
11
+ *
12
+ * @remarks
13
+ *
14
+ * outputStr(String.raw`
15
+ * a
16
+ * b`)
17
+ *
18
+ * 会输出
19
+ * a
20
+ * b
21
+ *
22
+ * 此功能可以用于输出多行字符串时,保持代码的缩进格式,而不会影响输出结果
23
+ *
24
+ * @param str : 要输出的字符串
25
+ * @param vars : 用于替换字符串中的变量
26
+ *
27
+ */
28
+ export function outputStr(str:string,vars?:Record<string,any> | any[]){
29
+ logsets.log(fixIndent(str),vars)
30
+ }
31
+
32
+ /**
33
+ * 修正多行字符串的缩进
34
+ *
35
+ * @param text
36
+ * @param indent
37
+ * @returns
38
+ */
39
+ export function fixIndent(text:string,indent?:boolean | number):string{
40
+ let indentValue = (indent==undefined || indent===true) ? 0 : (typeof(indent)=='number' ? indent : -1)
41
+ if(indentValue==-1) return text // 不修正缩进
42
+ let lines:string[] = text.split("\n")
43
+ let minSpaceCount = lines.reduce<number>((minCount,line,index)=>{
44
+ if(index==0) return minCount
45
+ const spaceCount = line.match(/^\s*/)?.[0].length || 0
46
+ return Math.min(minCount,spaceCount)
47
+ },9999)
48
+ lines = lines.map(line=>line.substring(minSpaceCount))
49
+ return lines.join("\n")
50
+ }
51
+
52
+ /**
53
+ * 增加内置选项
54
+ * @param command
55
+ */
56
+ export function addBuiltInOptions(command:any){
57
+ command.option("--work-dirs <values...>","指定工作目录",{hidden:true,optional:true,required:true,prompt:false})
58
+ command.option("--disable-prompts","禁用所有交互提示",{hidden:true,prompt:false})
59
+ command.option("--debug-cli","显示调试信息",{hidden:true,prompt:false})
60
+ }
61
+
62
+
63
+ /**
64
+ * 是否命令行中包含了--debug-cli选项
65
+ */
66
+ export function isDebug(){
67
+ return process.argv.includes("--debug-cli")
68
+ }
69
+
70
+
71
+ export function isDisablePrompts(){
72
+ return process.argv.includes("--disable-prompts")
73
+ }
74
+
75
+ /**
76
+ * 打印调试信息
77
+ * @param message
78
+ * @param args
79
+ */
80
+ export function outputDebug(message:string,...args:any[]){
81
+ let vars = (args.length == 1 && typeof(args[0])=='function') ? args[0]() : args
82
+ if(isDebug()) logsets.log(`[MixCli] ${message}`,...vars)
83
+ }
84
+
85
+ export const fileExists = promisify(fs.exists,{
86
+ parseCallback:(results)=>{
87
+ return results[0]
88
+ }
89
+ })
90
+ export const readFile = promisify(fs.readFile)
91
+ export const writeFile = promisify(fs.writeFile)
92
+ export const mkdir = promisify(fs.mkdir)
93
+
94
+ /**
95
+ * 创建目录
96
+ *
97
+ *
98
+ *
99
+ * @param {String[]} dirs 要创建的目录列表,类型为字符串数组
100
+ * @param callback 创建目录过程中的回调函数,类型为异步函数,接收一个参数 dir,表示当前正在创建的目录
101
+ * @returns 该函数返回一个 Promise 对象,表示创建目录的操作是否完成
102
+ */
103
+ export async function mkDirs(dirs:string[],{callback,base}:{callback?:Function,base?:string}){
104
+ if(!Array.isArray(dirs)) throw new Error("dirs参数必须为字符串数组")
105
+ for(let dir of dirs){
106
+ if(!path.isAbsolute(dir)) dir = path.join(base || process.cwd(),dir)
107
+ if(typeof(callback)=='function') callback(dir)
108
+ await mkdir(dir,{recursive:true})
109
+ }
110
+ }
111
+
112
+ export function showError(e:any){
113
+ if(isDebug()){
114
+ outputDebug("导入命令<>出错:{}",e.stack)
115
+ }else{
116
+ console.error(e)
117
+ }
118
+
119
+ }
120
+
121
+ // 编写一个函数,用于将使用-分隔的字符串转换为驼峰式
122
+ export function hyphenToCamelCase(str:string){
123
+ return str.replace(/-([a-z])/g,(_,letter)=>letter.toUpperCase())
124
+ }
125
+
126
+ export function getId(){
127
+ return Math.random().toString(36).substr(2)
128
+ }
129
+
130
+
131
+ export async function importModule(file:string){
132
+ let module
133
+ try{
134
+ module = require(file)
135
+ }catch(e:any){
136
+ try{
137
+ const cmd = await import(`file://${file}`)
138
+ module = cmd.default
139
+ }catch(e:any){
140
+ throw e
141
+ }
142
+ }
143
+ return module
144
+ }