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/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
+ }