@tremho/mist-lift 2.2.8 → 2.4.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.
Files changed (66) hide show
  1. package/README.md +9 -1
  2. package/build/commands/actions/updateDeployedPermissions.js +109 -0
  3. package/build/commands/actions/updateDeployedPermissions.js.map +1 -0
  4. package/build/commands/builtin/ApiDocMaker.js +18 -10
  5. package/build/commands/builtin/ApiDocMaker.js.map +1 -1
  6. package/build/commands/builtin/BuiltInHandler.js +6 -3
  7. package/build/commands/builtin/BuiltInHandler.js.map +1 -1
  8. package/build/commands/builtin/ExportWebroot.js +242 -0
  9. package/build/commands/builtin/ExportWebroot.js.map +1 -0
  10. package/build/commands/builtin/StageWebrootZip.js +10 -6
  11. package/build/commands/builtin/StageWebrootZip.js.map +1 -1
  12. package/build/commands/builtin/prebuilt-zips/API.zip +0 -0
  13. package/build/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
  14. package/build/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
  15. package/build/commands/builtin/webroot-export/s3webroot.js +117 -0
  16. package/build/commands/builtin/webroot-export/s3webroot.js.map +1 -0
  17. package/build/commands/deploy.js +6 -4
  18. package/build/commands/deploy.js.map +1 -1
  19. package/build/commands/package.js +31 -1
  20. package/build/commands/package.js.map +1 -1
  21. package/build/commands/publish.js +40 -13
  22. package/build/commands/publish.js.map +1 -1
  23. package/build/commands/start.js +2 -1
  24. package/build/commands/start.js.map +1 -1
  25. package/build/commands/update.js +1 -0
  26. package/build/commands/update.js.map +1 -1
  27. package/build/expressRoutes/all.js +1 -0
  28. package/build/expressRoutes/all.js.map +1 -1
  29. package/build/expressRoutes/functionBinder.js +159 -17
  30. package/build/expressRoutes/functionBinder.js.map +1 -1
  31. package/build/lib/DirectoryUtils.js +2 -1
  32. package/build/lib/DirectoryUtils.js.map +1 -1
  33. package/build/lib/IdSrc.js +29 -5
  34. package/build/lib/IdSrc.js.map +1 -1
  35. package/build/lib/TypeCheck.js +1204 -0
  36. package/build/lib/TypeCheck.js.map +1 -0
  37. package/build/lib/executeCommand.js +1 -1
  38. package/build/lib/executeCommand.js.map +1 -1
  39. package/build/lib/openAPI/openApiConstruction.js +238 -54
  40. package/build/lib/openAPI/openApiConstruction.js.map +1 -1
  41. package/build/lift.js +1 -1
  42. package/build/lift.js.map +1 -1
  43. package/package.json +5 -2
  44. package/src/commands/actions/updateDeployedPermissions.ts +80 -0
  45. package/src/commands/builtin/ApiDocMaker.ts +17 -10
  46. package/src/commands/builtin/BuiltInHandler.ts +7 -2
  47. package/src/commands/builtin/ExportWebroot.ts +195 -0
  48. package/src/commands/builtin/StageWebrootZip.ts +13 -5
  49. package/src/commands/builtin/prebuilt-zips/API.zip +0 -0
  50. package/src/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
  51. package/src/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
  52. package/src/commands/builtin/webroot-export/s3webroot.ts +78 -0
  53. package/src/commands/deploy.ts +6 -4
  54. package/src/commands/package.ts +33 -2
  55. package/src/commands/publish.ts +37 -12
  56. package/src/commands/start.ts +2 -1
  57. package/src/commands/update.ts +1 -0
  58. package/src/expressRoutes/all.ts +1 -0
  59. package/src/expressRoutes/functionBinder.ts +152 -16
  60. package/src/lib/DirectoryUtils.ts +2 -1
  61. package/src/lib/IdSrc.ts +17 -4
  62. package/src/lib/TypeCheck.ts +1168 -0
  63. package/src/lib/executeCommand.ts +1 -1
  64. package/src/lib/openAPI/openApiConstruction.ts +225 -41
  65. package/src/lift.ts +1 -1
  66. package/templateData/function-main-ts +8 -1
@@ -13,7 +13,7 @@ export async function executeCommand (cmd: string, args: any[], cwd = '', consol
13
13
  }
14
14
  return await new Promise(resolve => {
15
15
  const cmdstr = cmd + ' ' + args.join(' ')
16
- // console.log('executing ', cmdstr, 'at', cwd)
16
+ // console.log('executing ', cmdstr, 'at', cwd, {consolePass})
17
17
  const opts = {
18
18
  cwd,
19
19
  env: Object.assign(env, process.env)
@@ -6,14 +6,21 @@ import fs from 'fs'
6
6
  import path from 'path'
7
7
  import { resolvePaths } from '../pathResolve'
8
8
  import * as ac from 'ansi-colors'
9
+ import { decoratedName, getAccountId } from '../IdSrc'
10
+ import { getAWSCredentials, getSettings } from '../LiftConfig'
11
+ import { parseConstraints, TypeConstraint } from '../TypeCheck'
12
+ import yaml from 'js-yaml'
9
13
 
10
14
  export async function buildOpenApi (
11
15
  defs: any[],
12
16
  includePrivate: boolean = false,
17
+ includeCORS: boolean = false,
13
18
  yamlFile?: string
14
19
  ): Promise<Uint8Array> {
15
20
  const builder = new OpenApiBuilder()
16
21
 
22
+ // console.log("buildOpenApi to "+yamlFile)
23
+
17
24
  const projectPaths = await resolvePaths()
18
25
  if (!projectPaths.verified) return new Uint8Array(0) // don't continue if not valid
19
26
 
@@ -67,12 +74,14 @@ export async function buildOpenApi (
67
74
  addTypeSchema(builder, schemaName, schema)
68
75
  }
69
76
  method = method.trim().toLowerCase()
70
- addFunctionMethod(pathDef, method, def)
77
+ await addFunctionMethod(pathDef, method, def, includeCORS)
71
78
  for (const param of parameters) {
72
79
  addParameter(pathDef, param)
73
80
  }
74
- addCORSOptionMethod(pathDef)
75
- const pathMap = def.pathMap ?? '/' + (def.name as string)
81
+ if (includeCORS) addCORSOptionMethod(pathDef)
82
+ let pathMap = def.pathMap ?? '/' + (def.name as string)
83
+ pathMap = pathMap.replace('?', '')
84
+
76
85
  // pathMap = stagePathSegment + pathMap
77
86
  builder.addPath(pathMap, pathDef)
78
87
 
@@ -93,72 +102,230 @@ export async function buildOpenApi (
93
102
  return bufView
94
103
  }
95
104
 
96
- const yaml = builder.getSpecAsYaml()
97
- const bytes = str2ab(yaml)
98
- if (!includePrivate) {
99
- fs.writeFileSync(outFile, yaml)
100
- }
105
+ const spec = builder.getSpec()
106
+ spec['x-amazon-apigateway-binary-media-types'] = [
107
+ 'application/octet-stream',
108
+ 'image/*',
109
+ 'audio/*'
110
+ ]
111
+ const yamlSpec = yaml.dump(spec)
112
+ const bytes = str2ab(yamlSpec)
113
+ // if (!includePrivate) {
114
+ fs.writeFileSync(outFile, yamlSpec)
115
+ // }
101
116
  return bytes
102
117
  }
103
118
 
104
119
  function addTypeSchema (builder: any, schemaName: string, schema: any): void {
105
- const ref: any = { title: schemaName, type: 'object', properties: {} }
106
- for (const prop of Object.getOwnPropertyNames(schema)) {
107
- const scType: any = schemaType('', schema[prop], false)
108
-
109
- ref.properties[prop] = scType
120
+ let primType = ''
121
+ let type = schema?.type ?? 'Empty'
122
+ if (type === 'empty') type = 'Empty' // fix case
123
+ const isArray = (type.endsWith('[]'))
124
+ if (isArray) type = type.substring(0, type.length - 2)
125
+ if (type === 'string' || type === 'number' || type === 'object') primType = type
126
+ const mime = schema?.mime ?? primType ? 'text/plain' : 'application/json'
127
+ const sref = primType ? { type: primType } : { $ref: '#/components/schemas/' + type }
128
+ let ref: any = {}
129
+ if (isArray) {
130
+ ref = { type: 'array', items: sref }
131
+ } else {
132
+ ref = sref
110
133
  }
111
134
 
135
+ ref.title = schemaName
136
+
137
+ if (ref.type === 'object') ref.properties = {}
138
+
139
+ const constraints = schema.type !== 'object' && Array.isArray(schema?.constraints) ? parseConstraints(schema.type, schema.constraints.join('\n'), '\n') : undefined
140
+
141
+ const cdesc = (constraints != null) ? '\n - ' + (constraints.describe() ?? '').split('\n').join('\n - ') : ''
142
+ let description = schema.description
143
+ if (cdesc) description += cdesc
144
+
145
+ ref.description = description
146
+
147
+ const required: string[] = []
148
+
149
+ if (ref.properties) {
150
+ for (const [propName, propDef] of Object.entries(schema.properties || {})) {
151
+ const pda = propDef as any
152
+ const constraints = Array.isArray(pda?.constraints) ? parseConstraints(pda.type, pda.constraints.join('\n'), '\n') : undefined
153
+
154
+ const cdesc = (constraints != null) ? '\n - ' + (constraints.describe() ?? '').split('\n').join('\n - ') : ''
155
+ let description = (propDef as any).description
156
+ if (cdesc) description += cdesc
157
+
158
+ // if(constraints) {
159
+ // console.log("schema definition for "+schemaName+', prop '+propName)
160
+ // console.log(description)
161
+ // }
162
+
163
+ const propSchema: any = {
164
+ type: (propDef as any).type,
165
+ description
166
+ }
167
+
168
+ // You can add other custom handling here (e.g., enums, format, pattern)
169
+ ref.properties[propName] = propSchema
170
+
171
+ // Only mark as required if declared explicitly
172
+ if (schema.required && schema.required.includes(propName)) {
173
+ required.push(propName)
174
+ }
175
+ }
176
+
177
+ if (required.length > 0) {
178
+ ref.required = required
179
+ }
180
+ }
112
181
  builder.addSchema(schemaName, ref)
113
182
  }
114
183
 
115
- function addFunctionMethod (pathDef: any, method: string, def: any): void {
116
- // TODO: Define a return schema and put that here
184
+ async function addFunctionMethod (pathDef: any, method: string, def: any, includeCORS = true): Promise<void> {
117
185
  const retDef: any = (def.returns)['200']
118
186
  const content: any = {}
119
- const mime = retDef?.content ?? retDef?.mime ?? 'text/plain'
187
+ let primType = ''
188
+ let type = retDef?.type ?? 'Empty'
189
+ if (type === 'empty') type = 'Empty' // fix case
190
+ const isArray = (type.endsWith('[]'))
191
+ if (isArray) type = type.substring(0, type.length - 2)
192
+ if (type === 'string' || type === 'number' || type === 'object') primType = type
193
+ const mime = retDef?.mime ?? primType ? 'text/plain' : 'application/json'
194
+ const ref = primType ? { type: primType } : { $ref: '#/components/schemas/' + type }
195
+ let schema: any
196
+ if (isArray) {
197
+ schema = { type: 'array', items: ref }
198
+ } else {
199
+ schema = ref
200
+ }
120
201
  content[mime] = {
202
+ schema
121
203
  }
122
204
 
205
+ const region = getSettings()?.awsPreferredRegion ?? ''
206
+ const accountId = await getAccountId()
207
+ const decName = decoratedName(def.name)
208
+
209
+ const isBinary = def.bodyType && !def.bodyType.startsWith('text') && !def.bodyType.endsWith('json')
210
+
123
211
  const methData = {
124
212
  summary: def.name,
125
213
  description: def.description,
126
214
  responses: {
127
215
  200: {
128
216
  description: retDef?.description ?? 'Success Response',
217
+ headers: includeCORS
218
+ ? {
219
+ 'Access-Control-Allow-Origin': {
220
+ schema: {
221
+ type: 'string'
222
+ },
223
+ example: '*'
224
+ },
225
+ 'Access-Control-Allow-Headers': {
226
+ schema: {
227
+ type: 'string'
228
+ },
229
+ example: 'Content-Type, Authorization'
230
+ },
231
+ 'Access-Control-Allow-Methods': {
232
+ schema: {
233
+ type: 'string'
234
+ },
235
+ example: 'GET,POST,PUT,OPTIONS'
236
+ }
237
+ }
238
+ : undefined,
129
239
  content
130
240
  }
241
+ },
242
+ 'x-amazon-apigateway-integration': {
243
+ uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${region}:${accountId}:function:${decName}/invocations`,
244
+ passthroughbehavior: 'when_no_match',
245
+ contentHandling: isBinary ? 'CONVERT_TO_BINARY' : undefined,
246
+ httpMethod: 'POST', // always POST - this is how API gateway calls Lambda, not the method of the api
247
+ type: 'aws_proxy'
131
248
  }
132
249
  }
250
+
251
+ if (!includeCORS) {
252
+ // get the other response code declarations
253
+ for (const rcode of Object.getOwnPropertyNames(def.returns)) {
254
+ if (rcode != '200') {
255
+ const retDef = def.returns[rcode] ?? {}
256
+
257
+ const content: any = {}
258
+ let primType = ''
259
+ let type = retDef?.type ?? 'Empty'
260
+ if (type === 'empty') type = 'Empty' // fix case
261
+ const isArray = (type.endsWith('[]'))
262
+ if (isArray) type = type.substring(0, type.length - 2)
263
+ if (type === 'string' || type === 'number' || type === 'object') primType = type
264
+ const mime = retDef?.mime ?? primType ? 'text/plain' : 'application/json'
265
+ const ref = primType ? { type: primType } : { $ref: '#/components/schemas/' + type }
266
+ let schema: any
267
+ if (isArray) {
268
+ schema = { type: 'array', items: ref }
269
+ } else {
270
+ schema = ref
271
+ }
272
+ content[mime] = {
273
+ schema
274
+ }
275
+ retDef.content = content;
276
+ (methData.responses as any)[rcode] = retDef
277
+ }
278
+ }
279
+ }
280
+
133
281
  pathDef[method] = methData
134
282
  }
135
283
  function addCORSOptionMethod (pathDef: any): void {
136
- if (pathDef.options === undefined) return // already assinged by definition
284
+ // if (pathDef.options === undefined) return // already assinged by definition
137
285
  // add options for CORS
138
286
  pathDef.options = {
287
+ summary: 'CORS support',
139
288
  responses: {
140
289
  200: {
141
- description: '200 response',
142
- content: {
143
- 'application/json': {
290
+ description: 'CORS response',
291
+ headers: {
292
+ 'Access-Control-Allow-Origin': {
144
293
  schema: {
145
- $ref: '#/components/schemas/Empty'
146
- }
294
+ type: 'string'
295
+ },
296
+ example: '*'
297
+ },
298
+ 'Access-Control-Allow-Methods': {
299
+ schema: {
300
+ type: 'string'
301
+ },
302
+ example: 'GET,POST,PUT,OPTIONS'
303
+ },
304
+ 'Access-Control-Allow-Headers': {
305
+ schema: {
306
+ type: 'string'
307
+ },
308
+ example: 'Content-Type, Authorization'
147
309
  }
148
310
  }
149
311
  }
150
312
  },
151
313
  'x-amazon-apigateway-integration': {
314
+ type: 'mock',
315
+ requestTemplates: {
316
+ 'application/json': '{"statusCode": 200}'
317
+ },
152
318
  responses: {
153
319
  default: {
154
- statusCode: '200'
320
+ statusCode: '200',
321
+ responseParameters: {
322
+ 'method.response.header.Access-Control-Allow-Origin': "'*'",
323
+ 'method.response.header.Access-Control-Allow-Methods': "'GET, POST, PUT, OPTIONS'",
324
+ 'method.response.header.Access-Control-Allow-Headers': "'Content-Type, Authorization'"
325
+ }
155
326
  }
156
327
  },
157
- requestTemplates: {
158
- 'application/json': '{"statusCode": 200}'
159
- },
160
- passthroughBehavior: 'when_no_match',
161
- type: 'mock'
328
+ passthroughBehavior: 'when_no_match'
162
329
  }
163
330
  }
164
331
  }
@@ -166,21 +333,37 @@ function addCORSOptionMethod (pathDef: any): void {
166
333
  function addParameter (pathDef: any, param: any): void {
167
334
  if (pathDef.parameters === undefined) pathDef.parameters = []
168
335
  const parameters = pathDef.parameters
169
- const example = param.example ?? param.default ?? ''
170
- const type = param.type ?? typeof example
171
- const deflt = param.default ?? example
172
-
173
- parameters.push({
174
- in: param.in,
175
- name: param.name,
176
- description: param.description,
177
- example,
178
- required: param.required,
179
- schema: schemaType(deflt, type, true)
180
- })
336
+ const example = param.example ?? ''
337
+ const type = param.type ?? 'string'
338
+ const deflt = param.default ?? ''
339
+
340
+ // parameter is always required if it comes from path
341
+ // never required if it comes from query
342
+ // and may or may not be if it comes from body (per def)
343
+ const src = param?.in?.trim().toLowerCase() ?? ''
344
+ let required = (src === 'path')
345
+ if (src === 'body') required = param.required ?? false
346
+
347
+ const constraints = Array.isArray(param.constraints) ? parseConstraints(param.type, param.constraints.join('\n'), '\n') : undefined
348
+
349
+ const cdesc = (constraints != null) ? '\n - ' + (constraints.describe() ?? '').split('\n').join('\n - ') : ''
350
+ let description = param.description
351
+ if (cdesc) description += cdesc
352
+
353
+ // don't declare parameters marked as 'body'. OpenAPI doesn't support that.
354
+ if (param.in === 'path' || param.in === 'query') {
355
+ parameters.push({
356
+ in: param.in,
357
+ name: param.name,
358
+ description,
359
+ example: example || undefined,
360
+ required,
361
+ schema: schemaType(deflt, type, true)
362
+ })
363
+ }
181
364
  }
182
365
 
183
- function schemaType (deflt: string, namedType: string, innerOnly: boolean): any {
366
+ function schemaType (deflt: string | undefined, namedType: string, innerOnly: boolean): any {
184
367
  if (typeof namedType === 'object') return namedType
185
368
  const typeFormat = namedType.split(':')
186
369
  let type = typeFormat[0]
@@ -204,5 +387,6 @@ function schemaType (deflt: string, namedType: string, innerOnly: boolean): any
204
387
  }
205
388
  if (type === 'int') type = 'integer'
206
389
  if (type === 'bool') type = 'boolean'
390
+ if (!deflt) deflt = undefined
207
391
  return innerOnly ? { type, format, example: deflt } : { schema: { type, format, example: deflt } }
208
392
  }
package/src/lift.ts CHANGED
@@ -64,7 +64,7 @@ async function processCommand (): Promise<void> {
64
64
  }
65
65
  return
66
66
  case 'publish': {
67
- const ret = await doPublishAsync()
67
+ const ret = await doPublishAsync(args)
68
68
  process.exit(ret)
69
69
  }
70
70
  return
@@ -1,4 +1,11 @@
1
- import {LambdaApi, Success, NotFound, NotImplemented, ServerError} from "@tremho/inverse-y"
1
+ import {
2
+ LambdaApi,
3
+ getAssetUrl,
4
+ Success,
5
+ NotFound,
6
+ NotImplemented,
7
+ ServerError,
8
+ } from "@tremho/inverse-y"
2
9
  import fs from "fs"
3
10
  import path from 'path'
4
11
  import {Log} from "@tremho/inverse-y"