galbe 0.10.1 → 0.12.0

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/.prettierrc CHANGED
@@ -1,6 +1,6 @@
1
1
  tabWidth: 2
2
2
  useTabs: false
3
- trailingComma: none
3
+ trailingComma: es5
4
4
  semi: false
5
5
  singleQuote: true
6
6
  bracketSpacing: true
package/README.md CHANGED
@@ -20,6 +20,9 @@ cd app
20
20
  bun install && bun dev
21
21
  ```
22
22
 
23
+ The Galbe CLI ships with the package. You can invoke it with `bunx galbe` or
24
+ install it globally using `bun install -g galbe`.
25
+
23
26
  ## Documentation
24
27
 
25
28
  The detailed documentation is available at [galbe.dev](https://galbe.dev).
@@ -5,7 +5,7 @@ import { Command, Option } from 'commander'
5
5
  import { resolve, extname } from 'path'
6
6
  import { rm } from 'fs/promises'
7
7
  import { transformSync } from '@swc/core'
8
- import { CWD, fmtList, instanciateRoutes, silentExec } from '../../util'
8
+ import { CWD, fmtList, instanciateRoutes, silentExec, abbreviateVar } from '../../util'
9
9
  import { $T, Galbe, GalbeCLICommand, Method, Route, STResponse } from '../../../src'
10
10
  import { walkRoutes } from '../../../src/util'
11
11
  import { schemaToTypeStr, Optional, STSchema } from '../../../src/schema'
@@ -36,7 +36,7 @@ export default (cmd: Command) => {
36
36
  let pckg: any = {}
37
37
  try {
38
38
  pckg = await Bun.file(resolve(CWD, 'package.json')).json()
39
- } catch (e) { }
39
+ } catch (e) {}
40
40
 
41
41
  let error = null
42
42
  Bun.write(Bun.stdout, '💻 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m client\x1b[0m')
@@ -63,7 +63,7 @@ export default (cmd: Command) => {
63
63
  patch: [],
64
64
  delete: [],
65
65
  options: [],
66
- head: []
66
+ head: [],
67
67
  }
68
68
  const types: Record<string, STResponse> = {}
69
69
  let commands: GalbeCLICommand[] = []
@@ -75,7 +75,7 @@ export default (cmd: Command) => {
75
75
  walkRoutes(g.router.routes, r => {
76
76
  let meta = metaRoutes?.[r.path]?.[r.method]
77
77
  let [_, summary, description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
78
- if(!summary) description = meta?.head
78
+ if (!summary) description = meta?.head
79
79
  let route = {
80
80
  ...r,
81
81
  ...(meta?.operationId ? { alias: meta?.operationId } : {}),
@@ -89,36 +89,51 @@ export default (cmd: Command) => {
89
89
  {
90
90
  ...(r.schema?.params?.[m?.[1]]
91
91
  ? {
92
- type: schemaToTypeStr(r.schema.params[m[1]]),
93
- ...(r.schema.params[m[1]]?.description
94
- ? { description: r.schema.params[m[1]].description as string }
95
- : {})
96
- }
97
- : { type: 'string' })
98
- }
92
+ type: schemaToTypeStr(r.schema.params[m[1]]),
93
+ ...(r.schema.params[m[1]]?.description
94
+ ? { description: r.schema.params[m[1]].description as string }
95
+ : {}),
96
+ }
97
+ : { type: 'string' }),
98
+ },
99
99
  ])
100
100
  ) || {},
101
+ contentTypes: Object.keys(r.schema.body || { default: '' })
102
+ .map(s => `'${s}'`)
103
+ .join('|'),
101
104
  schemas: {
102
105
  ...(r.schema.headers ? { headers: schemaToTypeStr($T.object(r.schema.headers)) } : {}),
103
106
  ...(r.schema.query ? { query: schemaToTypeStr($T.object(r.schema.query)) } : {}),
104
- ...(r.schema.body ? { body: schemaToTypeStr(r.schema.body) } : {}),
107
+ ...(r.schema.body
108
+ ? {
109
+ body: `{${Object.entries(r.schema.body)
110
+ .map(([ct, s]) => `${ct}: ${schemaToTypeStr(s)}`)
111
+ .join(';')}}`,
112
+ }
113
+ : {}),
105
114
  ...(r.schema.response
106
115
  ? {
107
- response: Object.fromEntries(
108
- Object.entries(r.schema.response).map(([k, v]) => [k === 'default' ? '"default"' : k, schemaToTypeStr(v as STSchema)])
109
- )
110
- }
111
- : {})
112
- }
116
+ response: Object.fromEntries(
117
+ Object.entries(r.schema.response).map(([k, v]) => [
118
+ k === 'default' ? '"default"' : k,
119
+ schemaToTypeStr(v as STSchema),
120
+ ])
121
+ ),
122
+ }
123
+ : {}),
124
+ },
113
125
  }
114
- Object.values(r.schema.response || {}).filter(s => s?.id).forEach(s => {
115
- //@ts-ignore
116
- types[s.id] = schemaToTypeStr(s)
117
- })
126
+ Object.values(r.schema.response || {})
127
+ .filter(s => s?.id)
128
+ .forEach(s => {
129
+ //@ts-ignore
130
+ types[s.id] = schemaToTypeStr(s)
131
+ })
118
132
  routes[r.method.toLocaleLowerCase()].push(route)
119
133
  if (target === 'cli' && meta?.operationId)
120
134
  commands.push({
121
135
  name: meta.operationId,
136
+ tags: meta?.tags ? (Array.isArray(meta.tags) ? meta.tags : [meta.tags]) : [],
122
137
  description: route.summary || route.description,
123
138
  route,
124
139
  arguments:
@@ -127,7 +142,7 @@ export default (cmd: Command) => {
127
142
  return {
128
143
  name: k,
129
144
  type: p.type === 'boolean' ? '' : `<${p.type}>`,
130
- description: p?.description || ''
145
+ description: p?.description || '',
131
146
  }
132
147
  }
133
148
  ) || [],
@@ -136,12 +151,12 @@ export default (cmd: Command) => {
136
151
  let type = schemaToTypeStr({ ...o, [Optional]: false })
137
152
  return {
138
153
  name: k,
139
- short: k[0],
154
+ short: abbreviateVar(k),
140
155
  type: type === 'boolean' ? '' : `<${type}>`,
141
156
  description: o?.description || '',
142
- default: o.default
157
+ default: o.default,
143
158
  }
144
- }) || []
159
+ }) || [],
145
160
  })
146
161
  })
147
162
 
@@ -167,11 +182,11 @@ export default (cmd: Command) => {
167
182
  filled = transformSync(filled, {
168
183
  jsc: {
169
184
  parser: {
170
- syntax: 'typescript'
185
+ syntax: 'typescript',
171
186
  },
172
187
  preserveAllComments: true,
173
- target: 'esnext'
174
- }
188
+ target: 'esnext',
189
+ },
175
190
  }).code
176
191
  }
177
192
 
@@ -182,6 +197,19 @@ export default (cmd: Command) => {
182
197
  // Plugin CLI hook
183
198
  if (commands) for (let p of g.plugins) if (p.cli) await p.cli(commands)
184
199
 
200
+ const tags = commands.reduce(
201
+ (p, c) => {
202
+ if (c.tags.length)
203
+ for (const t of c.tags) {
204
+ if (!(t in p)) p[t] = []
205
+ p[t].push(c)
206
+ }
207
+ else p[''].push(c)
208
+ return p
209
+ },
210
+ { '': [] as GalbeCLICommand[] }
211
+ )
212
+
185
213
  let filled = file.replaceAll(/\/\*\%([\s\S]*?)\%\*\//g, (_match, p) => {
186
214
  let idt = p.match(/^\n*([ \t]*)/, p)?.[1] || ''
187
215
  const script = new Script(p)
@@ -190,7 +218,7 @@ export default (cmd: Command) => {
190
218
  name: pckg?.name || 'Galbe app CLI',
191
219
  description: pckg?.description || '',
192
220
  version: pckg?.version || '0.1.0',
193
- commands
221
+ tags,
194
222
  }
195
223
  createContext(sandbox)
196
224
  let res = script.runInNewContext(sandbox)
@@ -3,6 +3,7 @@ import { transformSync } from '@swc/core'
3
3
  import { resolve, relative, dirname } from 'path'
4
4
  import { load as ymlLoad } from 'js-yaml'
5
5
  import { OpenAPIV3 } from 'openapi-types'
6
+ import { inferBodyType } from '../../../../src/util'
6
7
 
7
8
  type SchemaEntry = {
8
9
  key: string
@@ -72,7 +73,7 @@ const serialize = (obj: any) => {
72
73
  return JSON.stringify(obj, (k, value) => {
73
74
  if (k === 'pattern' && value) return `/${value}/`
74
75
  return value
75
- }).replace(/"\/(.*)\/([gimsuy]*)"/g, '/$1/$2');
76
+ }).replace(/"\/(.*)\/([gimsuy]*)"/g, '/$1/$2')
76
77
  }
77
78
 
78
79
  const writeCodeFile = async (path: string, content: string, target: 'js' | 'ts') => {
@@ -80,11 +81,11 @@ const writeCodeFile = async (path: string, content: string, target: 'js' | 'ts')
80
81
  content = transformSync(content, {
81
82
  jsc: {
82
83
  parser: {
83
- syntax: 'typescript'
84
+ syntax: 'typescript',
84
85
  },
85
86
  preserveAllComments: true,
86
- target: 'esnext'
87
- }
87
+ target: 'esnext',
88
+ },
88
89
  }).code
89
90
  }
90
91
  await Bun.write(`${path}.${target}`, content)
@@ -99,7 +100,8 @@ const parseOapiSchema = (
99
100
  return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
100
101
  }
101
102
  //@ts-ignore
102
- if (os?.$ref) return `%ref:${os.$ref}%`
103
+ const ref: string | undefined = os?.$ref
104
+ if (ref) return `%ref:${ref}%`
103
105
  os = os as OpenAPIV3.SchemaObject
104
106
  let options: typeof details & {
105
107
  min?: number
@@ -115,30 +117,51 @@ const parseOapiSchema = (
115
117
  } = {
116
118
  ...details,
117
119
  title: os.title,
118
- description: details.description || os.description
120
+ description: details.description || os.description,
119
121
  }
120
- if(!os?.type) return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
122
+
121
123
  let resp = ''
122
124
  let hasOptions = Object.values(options).some(v => !!v)
123
125
  let optArg = hasOptions ? serialize(options) : ''
124
- let anyOf = os.oneOf || os.anyOf || os.allOf
126
+ let anyOf = os.oneOf || os.anyOf
127
+ let allOf = os.allOf
125
128
  let required = os.required
126
129
  let nullable = os.nullable
127
130
 
128
- if (anyOf?.length) {
131
+ if (os.discriminator) {
132
+ const propName = os.discriminator.propertyName
133
+ const mapEntries = Object.entries(os.discriminator.mapping || {})
134
+ resp = `$T.union([${mapEntries
135
+ .map(
136
+ ([k, s]) =>
137
+ `$T.intersection([$T.object({\"${propName}\":$T.literal(\"${k}\")}),${parseOapiSchema({
138
+ $ref: s,
139
+ })}])`
140
+ )
141
+ .join(',')}], ${serialize(options)})`
142
+ } else if (anyOf?.length) {
129
143
  if (anyOf.length === 1) resp = parseOapiSchema(anyOf[0] as OpenAPIV3.SchemaObject, details, extra)
130
144
  else {
131
145
  resp = `$T.union([${anyOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${serialize(
132
146
  options
133
147
  )})`
134
148
  }
149
+ } else if (allOf?.length) {
150
+ if (allOf.length === 1) resp = parseOapiSchema(allOf[0] as OpenAPIV3.SchemaObject, details, extra)
151
+ else {
152
+ resp = `$T.intersection([${allOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${serialize(
153
+ options
154
+ )})`
155
+ }
156
+ } else if (!os?.type) {
157
+ return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
135
158
  } else if (os.type === 'boolean') resp = `$T.boolean(${hasOptions ? serialize(options) : ''})`
136
159
  else if (os.type === 'number') {
137
160
  let { max, min, exclusiveMax, exclusiveMin } = {
138
161
  max: os.maximum !== undefined && !os.exclusiveMaximum ? os.maximum : undefined,
139
162
  min: os.minimum !== undefined && !os.exclusiveMinimum ? os.minimum : undefined,
140
163
  exclusiveMax: os.maximum !== undefined && os.exclusiveMaximum ? os.maximum : undefined,
141
- exclusiveMin: os.minimum !== undefined && os.exclusiveMinimum ? os.minimum : undefined
164
+ exclusiveMin: os.minimum !== undefined && os.exclusiveMinimum ? os.minimum : undefined,
142
165
  }
143
166
  options = { ...options, min, max, exclusiveMax, exclusiveMin }
144
167
  hasOptions = Object.values(options).some(v => !!v)
@@ -169,14 +192,14 @@ const parseOapiSchema = (
169
192
  resp = `$T.array(${parseOapiSchema(os?.items)}, ${optArg})`
170
193
  } else if (os.type === 'object') {
171
194
  let props = Object.entries(os?.properties || {})
172
- .map(([k, v]) => `"${k}":${parseOapiSchema(v)}`)
173
- .join(',')
195
+ .map(([k, v]) => `"${k}":${parseOapiSchema(v)}`)
196
+ .join(',')
174
197
  if (extra?.media === 'multipart/form-data') {
175
- resp = `$T.multipartForm({${props}}${optArg ? `, ${optArg}`:''})`
198
+ resp = `$T.multipartForm({${props}}${optArg ? `, ${optArg}` : ''})`
176
199
  } else if (extra?.media === 'application/x-www-form-urlencoded') {
177
- resp = `$T.urlForm({${props}}${optArg ? `, ${optArg}`:''})`
200
+ resp = `$T.object({${props}}${optArg ? `, ${optArg}` : ''})`
178
201
  } else {
179
- resp = `$T.object({${props}}${optArg ? `, ${optArg}`:''})`
202
+ resp = `$T.object({${props}}${optArg ? `, ${optArg}` : ''})`
180
203
  }
181
204
  } else throw new Error(`Unknown schema type ${JSON.stringify(os)}`)
182
205
 
@@ -195,20 +218,17 @@ const buildSchemaIndex = (def: OpenAPIV3.Document) => {
195
218
  let dependsOn = new Set<string>()
196
219
  if (kind === 'schemas') schema = parseOapiSchema(s, { id: k })
197
220
  else if (kind === 'requestBodies' || kind === 'responses') {
198
- let schemas=[] as string[]
199
- if(!!s.content){
200
-
221
+ let schemas = [] as string[]
222
+ if (!!s.content) {
201
223
  schemas = [
202
224
  ...new Set(
203
- Object.entries((s as OpenAPIV3.RequestBodyObject)?.content || {null:{}}).map(([media, v]) => {
225
+ Object.entries((s as OpenAPIV3.RequestBodyObject)?.content || { null: {} }).map(([media, v]) => {
204
226
  return parseOapiSchema(v.schema, { id: k }, { media })
205
227
  })
206
- )
228
+ ),
207
229
  ]
208
230
  } else {
209
- schemas = [
210
- parseOapiSchema(undefined, {id: k, ...s})
211
- ]
231
+ schemas = [parseOapiSchema(undefined, { id: k, ...s })]
212
232
  }
213
233
  schema = schemas.length <= 0 ? '' : schemas.length === 1 ? schemas[0] : `$T.union([${schemas.join(',')}])`
214
234
  }
@@ -222,7 +242,7 @@ const buildSchemaIndex = (def: OpenAPIV3.Document) => {
222
242
  prefix: '',
223
243
  schema,
224
244
  dependsOn,
225
- usedBy: new Set()
245
+ usedBy: new Set(),
226
246
  }
227
247
  }
228
248
  for (let [k, v] of Object.entries(def.components?.schemas || {})) initSchema(k, v, 'schemas')
@@ -246,7 +266,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
246
266
 
247
267
  let meta = '/**\n'
248
268
  if (def.summary) meta += ` * ${def.summary}\n *\n`
249
- if (def.description) meta += ` * ${def.description.replace('\n', '\n * ')}\n`
269
+ if (def.description) meta += ` * ${def.description.replace(/\n/g, '\n * ')}\n`
250
270
  if (def.operationId) meta += ` * @operationId ${def.operationId}\n`
251
271
  if (def.externalDocs) meta += ` * @externalDocs ${def.externalDocs}\n`
252
272
  if (def.tags) meta += ` * @tags ${def.tags.join(' ')}\n`
@@ -259,9 +279,13 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
259
279
  // @ts-ignore: TODO handle refs cases
260
280
  if (_p.$ref) continue
261
281
  let p = _p as OpenAPIV3.ParameterObject
262
- let o = (s: string) => (p.in !== 'path' && !p.required && !/^\$T.optional\(.*\)$/.test(s) ? `$T.optional(${s})` : s)
282
+ let o = (s: string) => {
283
+ const [isOptional, so] = [...(s.match(/^\$T.optional\((.*)\)$/) || [])]
284
+ s = so ?? s
285
+ return p.in !== 'path' && !p.required && !isOptional ? `$T.optional(${s})` : s
286
+ }
263
287
  sp[p.in][p.name] = o(
264
- unref(parseOapiSchema(p.schema, {description: p.description }), m => {
288
+ unref(parseOapiSchema(p.schema, { description: p.description }), m => {
265
289
  let l = m.split('/')
266
290
  imports[l[l.length - 1]] = m
267
291
  return l[l.length - 1]
@@ -272,7 +296,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
272
296
  let [schemaParams, schemaQuery, schemaHeaders] = [
273
297
  { g: 'params', o: 'path' },
274
298
  { g: 'query', o: 'query' },
275
- { g: 'headers', o: 'header' }
299
+ { g: 'headers', o: 'header' },
276
300
  ].map(({ g, o }) =>
277
301
  Object.keys(sp[o]).length
278
302
  ? ` ${g}: {${Object.entries(sp[o])
@@ -282,7 +306,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
282
306
  )
283
307
 
284
308
  let body = ''
285
- if(!['get', 'delete', 'options', 'head'].includes(method)){
309
+ if (!['get', 'delete', 'options', 'head'].includes(method)) {
286
310
  let _rb = def?.requestBody as OpenAPIV3.ReferenceObject
287
311
  if (_rb?.$ref) {
288
312
  body = unref(` body: %ref:${_rb.$ref}%`, m => {
@@ -295,16 +319,17 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
295
319
  let o = (s: string) => (!rb?.required ? `$T.optional(${s})` : s)
296
320
  let bs = [
297
321
  ...new Set(
298
- Object.entries(rb?.content || {null:{}}).map(([media, v]) =>
322
+ Object.entries(rb?.content || { null: {} }).map(([media, v]) => [
323
+ media,
299
324
  unref(parseOapiSchema(v.schema, undefined, { media }), m => {
300
325
  let l = m.split('/')
301
326
  imports[l[l.length - 1]] = m
302
327
  return l[l.length - 1]
303
- })
304
- )
305
- )
328
+ }),
329
+ ])
330
+ ),
306
331
  ]
307
- body = bs.length === 1 ? ` body: ${o(bs[0])}` : bs.length > 1 ? ` body: ${o(`$T.union([${bs.join(',')}])`)}` : ''
332
+ body = bs.length ? ` body: {${bs.map(([k, v]) => `"${inferBodyType(k)}":${o(v)}`).join(',')}}` : ''
308
333
  }
309
334
  }
310
335
 
@@ -316,11 +341,13 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
316
341
  let s: string = Number.isInteger(Number(status)) ? status : 'default'
317
342
 
318
343
  //@ts-ignore
319
- let rootRef = sv?.$ref ? unref(parseOapiSchema(sv), m => {
320
- let l = m.split('/')
321
- imports[l[l.length - 1]] = m
322
- return l[l.length - 1]
323
- }) : null
344
+ let rootRef = sv?.$ref
345
+ ? unref(parseOapiSchema(sv), m => {
346
+ let l = m.split('/')
347
+ imports[l[l.length - 1]] = m
348
+ return l[l.length - 1]
349
+ })
350
+ : null
324
351
  if (rootRef) {
325
352
  return [s, [rootRef]]
326
353
  }
@@ -351,12 +378,12 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
351
378
  schema: {
352
379
  name: schemaName,
353
380
  imports,
354
- def: schema.length ? `{\n${schema.join(',\n')}\n}` : ''
381
+ def: schema.length ? `{\n${schema.join(',\n')}\n}` : '',
355
382
  },
356
383
  endpoint: {
357
384
  meta,
358
- def: endpoint
359
- }
385
+ def: endpoint,
386
+ },
360
387
  }
361
388
  }
362
389
 
@@ -383,7 +410,7 @@ const parseEndpoints = (def: OpenAPIV3.Document) => {
383
410
  scope,
384
411
  path,
385
412
  schema,
386
- endpoint
413
+ endpoint,
387
414
  }
388
415
  }
389
416
  }
@@ -399,7 +426,7 @@ const writeFiles = async (
399
426
  const typeMap = {
400
427
  schemas: 'commons',
401
428
  requestBodies: 'requests',
402
- responses: 'responses'
429
+ responses: 'responses',
403
430
  }
404
431
  const parseSchemasToFile = (
405
432
  schemas: Record<string, SchemaEntry>,
@@ -436,7 +463,7 @@ const writeFiles = async (
436
463
  const sMaps = [
437
464
  { g: 'commons', o: 'schemas' },
438
465
  { g: 'requests', o: 'requestBodies' },
439
- { g: 'responses', o: 'responses' }
466
+ { g: 'responses', o: 'responses' },
440
467
  ] as const
441
468
  for (let { g, o } of sMaps) {
442
469
  let s = parseSchemasToFile(
@@ -4,7 +4,7 @@ import { program, Option } from 'commander'
4
4
  import { resolve } from 'path'
5
5
 
6
6
  const DEFAULT_HEADERS = {
7
- 'user-agent': 'Galbe//*%(()=>version)()%*//cli'
7
+ 'user-agent': 'Galbe//*%(()=>version)()%*//cli',
8
8
  }
9
9
  const ansi = (p, c, str) => (p ? `\x1b[${c}m${str}\x1b[0m` : str)
10
10
 
@@ -73,9 +73,9 @@ const fetchApi = async (method, path, props) => {
73
73
  headers: {
74
74
  ...DEFAULT_HEADERS,
75
75
  ...(bodyFile ? { 'content-type': 'application/octet-stream' } : {}),
76
- ...(headers || {})
76
+ ...(headers || {}),
77
77
  },
78
- ...(body ? { body } : {})
78
+ ...(body ? { body } : {}),
79
79
  })
80
80
  let endTime = Bun.nanoseconds() - startTime
81
81
  if (format.has('s')) fmtRes(res.status, format.has('p'))
@@ -99,21 +99,23 @@ const formatDefault = def =>
99
99
  ? `[${def.map(d => formatDefault(d)).join(',')}]`
100
100
  : def ?? 'undefined';
101
101
 
102
- result = commands.map(c=>{
103
- let args = c.arguments.map(a=>`.argument("${a.name}", "${JSON.stringify(a.description).slice(1,-1) || a.name+' argument' || ''}")`)
104
- let optionsBase = [
105
- {name: '%format', short:'%f', type: '[string]', description: 'response format [\'s\',\'h\',\'b\',\'t\',\'p\']', default:["s","b","p"]},
106
- {name: '%header', short:'%h', type: '<string...>', description: 'request header formated as headerName=headerValue', default:[]},
107
- {name: '%query', short:'%q', type: '<string...>', description: 'query param formated as paramName=paramValue', default:[]},
108
- {name: '%body', short:'%b', type: '<string>', description: 'request body', default:''},
109
- {name: '%bodyFile', short:'%bf', type: '<path>', description: 'request body file', default:''}
110
- ]
111
- let options = [...optionsBase,...(c.options||[])].map(o=>`.addOption(new Option("-${o.short}, --${o.name} ${o.type}", "${JSON.stringify(o.description).slice(1,-1)}").default(${formatDefault(o.default)}))`)
112
- let action = `.action(async (${c.arguments.map(a=>`${a.name},`).join('')} props) => {
113
- ${c.action ? ';('+c.action.toString()+')(props)' : ''}
114
- return await fetchApi("${c.route.method.toUpperCase()}",\`${c.route.pathT}\`, props)
115
- })`
116
- return `program.command("${c.name}").description("${JSON.stringify(c.description).slice(1,-1)}")${args.join('')}${options.join('')}${action}`
102
+ result = Object.entries(tags).map(([tag, commands])=>{
103
+ return (tag?`const _${tag} = program.command('${tag}');\n`:'')+commands.map(c=>{
104
+ let args = c.arguments.map(a=>`.argument("${a.name}", "${JSON.stringify(a.description).slice(1,-1) || a.name+' argument' || ''}")`)
105
+ let optionsBase = [
106
+ {name: '%format', short:'%f', type: '[string]', description: 'response format [\'s\',\'h\',\'b\',\'t\',\'p\']', default:["s","b","p"]},
107
+ {name: '%header', short:'%h', type: '<string...>', description: 'request header formated as headerName=headerValue', default:[]},
108
+ {name: '%query', short:'%q', type: '<string...>', description: 'query param formated as paramName=paramValue', default:[]},
109
+ {name: '%body', short:'%b', type: '<string>', description: 'request body', default:''},
110
+ {name: '%bodyFile', short:'%bf', type: '<path>', description: 'request body file', default:''}
111
+ ]
112
+ let options = [...optionsBase,...(c.options||[])].map(o=>`.addOption(new Option("-${o.short}, --${o.name} ${o.type}", "${JSON.stringify(o.description).slice(1,-1)}").default(${formatDefault(o.default)}))`)
113
+ let action = `.action(async (${c.arguments.map(a=>`${a.name},`).join('')} props) => {
114
+ ${c.action ? ';('+c.action.toString()+')(props)' : ''}
115
+ return await fetchApi("${c.route.method.toUpperCase()}",\`${c.route.pathT}\`, props)
116
+ })`
117
+ return `${tag?`_${tag}`:'program'}.command("${c.name}").description("${JSON.stringify(c.description).slice(1,-1)}")${args.join('')}${options.join('')}${action}`
118
+ }).join(';\n')
117
119
  })
118
120
  %*/
119
121