@tremho/mist-lift 1.1.4 → 1.1.5
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/LICENSE +21 -21
- package/README.md +70 -66
- package/build/QSTest/functions/IntegrationTest/src/main.js +1 -1
- package/build/src/commands/builtin/ApiDocMaker.js +0 -1
- package/build/src/commands/builtin/ApiDocMaker.js.map +1 -1
- package/build/src/commands/doctor.js +12 -1
- package/build/src/commands/doctor.js.map +1 -1
- package/build/src/lib/LiftVersion.js +1 -1
- package/build/src/lib/LiftVersion.js.map +1 -1
- package/package.json +79 -79
- package/src/commands/actions/initQuestions.ts +133 -133
- package/src/commands/actions/setupPackageJson.ts +32 -32
- package/src/commands/build.ts +173 -173
- package/src/commands/builtin/ApiDocMaker.ts +105 -106
- package/src/commands/builtin/BuiltInHandler.ts +47 -47
- package/src/commands/builtin/DeployBuiltInZip.ts +25 -25
- package/src/commands/builtin/StageWebrootZip.ts +36 -36
- package/src/commands/create.ts +52 -52
- package/src/commands/deploy.ts +161 -161
- package/src/commands/doctor.ts +118 -107
- package/src/commands/help.ts +178 -178
- package/src/commands/info.ts +42 -42
- package/src/commands/init.ts +61 -61
- package/src/commands/package.ts +234 -234
- package/src/commands/publish.ts +330 -330
- package/src/commands/settings.ts +73 -73
- package/src/commands/start.ts +43 -43
- package/src/commands/test.ts +37 -37
- package/src/commands/user.ts +20 -20
- package/src/expressRoutes/all.ts +99 -99
- package/src/expressRoutes/api.ts +22 -22
- package/src/expressRoutes/functionBinder.ts +155 -155
- package/src/integration-tests/quickstart-scenario.test.ts +76 -76
- package/src/lib/CaseUtils.ts +63 -63
- package/src/lib/DirectoryUtils.ts +34 -34
- package/src/lib/LiftConfig.ts +74 -74
- package/src/lib/LiftVersion.ts +87 -87
- package/src/lib/Tests/fileCompare.test.ts +35 -35
- package/src/lib/askQuestion.ts +17 -17
- package/src/lib/executeCommand.ts +45 -45
- package/src/lib/fileCompare.ts +55 -55
- package/src/lib/openAPI/ApiBuildCollector.ts +47 -47
- package/src/lib/openAPI/WebrootFileSupport.ts +19 -19
- package/src/lib/openAPI/openApiConstruction.ts +196 -196
- package/src/lib/pathResolve.ts +26 -26
- package/src/lib/utils.ts +43 -43
- package/src/lift.ts +87 -87
- package/templateData/function-definition-template +20 -20
- package/templateData/function-local-ts +16 -16
- package/templateData/function-main-ts +16 -16
- package/templateData/function-runmain-mjs +6 -6
- package/templateData/function-test-template +11 -11
- package/templateData/swagger-ui-bundle.js +2 -2
- package/templateData/swagger-ui-standalone-preset.js +2 -2
- package/templateData/swagger-ui.css +2 -2
- package/tsconfig.json +28 -28
- package/build/commands/builtin/prebuilt-zips/API.zip +0 -0
- package/build/commands/builtin/prebuilt-zips/FileServe.zip +0 -0
- package/build/commands/builtin/prebuilt-zips/Webroot.zip +0 -0
package/src/commands/publish.ts
CHANGED
|
@@ -1,330 +1,330 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
LambdaClient,
|
|
4
|
-
ListFunctionsCommand
|
|
5
|
-
} from '@aws-sdk/client-lambda'
|
|
6
|
-
import {
|
|
7
|
-
APIGatewayClient,
|
|
8
|
-
ImportRestApiCommand,
|
|
9
|
-
GetRestApisCommand,
|
|
10
|
-
GetResourcesCommand, GetResourcesCommandOutput,
|
|
11
|
-
DeleteRestApiCommand,
|
|
12
|
-
PutIntegrationRequest,
|
|
13
|
-
PutIntegrationCommand,
|
|
14
|
-
CreateDeploymentCommand
|
|
15
|
-
} from '@aws-sdk/client-api-gateway'
|
|
16
|
-
|
|
17
|
-
import { getAWSCredentials, getSettings } from '../lib/LiftConfig'
|
|
18
|
-
|
|
19
|
-
import * as ac from 'ansi-colors'
|
|
20
|
-
|
|
21
|
-
import path from 'path'
|
|
22
|
-
import fs, { mkdirSync } from 'fs'
|
|
23
|
-
import os from 'os'
|
|
24
|
-
import { resolvePaths } from '../lib/pathResolve'
|
|
25
|
-
import { gatherFunctionDefinitions } from '../lib/openAPI/ApiBuildCollector'
|
|
26
|
-
import { delay } from '../lib/utils'
|
|
27
|
-
import { addBuiltInDefinitions, MakeBuiltinApiDoc } from './builtin/ApiDocMaker'
|
|
28
|
-
import { DeployApiBuiltin, DeployRootFileserves, DeployWebrootBuiltIn } from './builtin/BuiltInHandler'
|
|
29
|
-
import { getProjectName, getProjectVersion } from '../lib/LiftVersion'
|
|
30
|
-
import md5 from 'md5'
|
|
31
|
-
|
|
32
|
-
let projectPaths: any
|
|
33
|
-
|
|
34
|
-
export async function doPublishAsync (stageName?: string): Promise<number> {
|
|
35
|
-
// let retCode = await doDeployAsync([])
|
|
36
|
-
// if(retCode) return retCode
|
|
37
|
-
const retCode = 0
|
|
38
|
-
|
|
39
|
-
projectPaths = resolvePaths()
|
|
40
|
-
if (projectPaths.verified !== true) {
|
|
41
|
-
console.log(ac.bold.magenta('current directory is not at project root'))
|
|
42
|
-
return -1
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
await publishApi(stageName ?? 'Dev')
|
|
46
|
-
|
|
47
|
-
return retCode
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function publishApi (
|
|
51
|
-
stageName?: string
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
if (stageName === undefined) stageName = 'Dev'
|
|
54
|
-
console.log(ac.green.bold(`Publishing Api to ${stageName}`))
|
|
55
|
-
|
|
56
|
-
// do the built-in deploys
|
|
57
|
-
await DeployWebrootBuiltIn()
|
|
58
|
-
await DeployRootFileserves()
|
|
59
|
-
await DeployApiBuiltin()
|
|
60
|
-
|
|
61
|
-
// make a private api
|
|
62
|
-
// const fs = require('fs')
|
|
63
|
-
// const os = require('os')
|
|
64
|
-
// const path = require('path')
|
|
65
|
-
|
|
66
|
-
let tmpDir
|
|
67
|
-
let apiBytes
|
|
68
|
-
const appPrefix = 'MistLift'
|
|
69
|
-
try {
|
|
70
|
-
tmpDir = path.join(os.tmpdir(), appPrefix)
|
|
71
|
-
// console.log("making "+tmpDir)
|
|
72
|
-
mkdirSync(tmpDir, { recursive: true })
|
|
73
|
-
|
|
74
|
-
const yamlFile = path.join(tmpDir, 'papi.yaml')
|
|
75
|
-
await MakeBuiltinApiDoc(yamlFile)
|
|
76
|
-
// console.log("reading "+yamlFile)
|
|
77
|
-
apiBytes = fs.readFileSync(yamlFile)
|
|
78
|
-
} catch (e: any) {
|
|
79
|
-
console.error(ac.red.bold('Error creating temp staging folder ') + (e.message as string))
|
|
80
|
-
throw Error()
|
|
81
|
-
} finally {
|
|
82
|
-
try {
|
|
83
|
-
if (tmpDir !== undefined) {
|
|
84
|
-
fs.rmSync(tmpDir, { recursive: true })
|
|
85
|
-
}
|
|
86
|
-
} catch (e: any) {
|
|
87
|
-
console.error(`An error has occurred while removing the temp folder at ${tmpDir ?? ''}. Please remove it manually. Error: ${(e.message as string)}`)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const client = new APIGatewayClient(getAWSCredentials())
|
|
92
|
-
|
|
93
|
-
await RemoveExistingVersions(client)
|
|
94
|
-
|
|
95
|
-
const command = new ImportRestApiCommand({
|
|
96
|
-
failOnWarnings: false,
|
|
97
|
-
body: apiBytes
|
|
98
|
-
})
|
|
99
|
-
let apiId
|
|
100
|
-
try {
|
|
101
|
-
const response = await client.send(command)
|
|
102
|
-
const { name, id, version, warnings } = response
|
|
103
|
-
console.log(ac.grey(`\n\nAPI ${name ?? ''} version ${version ?? ''} [${id ?? ''}] schema created`))
|
|
104
|
-
apiId = id ?? ''
|
|
105
|
-
if (warnings?.length !== 0) {
|
|
106
|
-
console.log(ac.magenta(` with ${warnings?.length ?? 0} warnings`))
|
|
107
|
-
if (warnings !== undefined) {
|
|
108
|
-
for (const warning of warnings) {
|
|
109
|
-
console.log(ac.grey.italic(` ${warning}`))
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} catch (e: any) {
|
|
114
|
-
console.error(ac.bold.red(e.message))
|
|
115
|
-
}
|
|
116
|
-
// const apidoc = new TextDecoder().decode(apiBytes)
|
|
117
|
-
// console.log(apidoc)
|
|
118
|
-
if (apiId === '') return
|
|
119
|
-
|
|
120
|
-
console.log(ac.grey('Continuing with binding...'))
|
|
121
|
-
const prereq = await PrequisiteValues(apiId ?? '')
|
|
122
|
-
const intRequests = prereq.MakeRequests()
|
|
123
|
-
await PutIntegrations(intRequests)
|
|
124
|
-
await DeployApi(apiId ?? '', stageName)
|
|
125
|
-
const region = getSettings()?.awsPreferredRegion ?? ''
|
|
126
|
-
const publishUrl = `https://${apiId ?? ''}.execute-api.${region}.amazonaws.com/${stageName}`
|
|
127
|
-
console.log(ac.green.bold(`\n Successfully deployed to ${publishUrl}`))
|
|
128
|
-
recordLatestPublish(publishUrl)
|
|
129
|
-
}
|
|
130
|
-
function findApiName (): string {
|
|
131
|
-
const infoFile = path.join(projectPaths.functionPath, 'apiService.info.json')
|
|
132
|
-
const pkgFile = projectPaths.packagePath
|
|
133
|
-
let pkg: any = {}
|
|
134
|
-
if (fs.existsSync(pkgFile)) { pkg = JSON.parse(fs.readFileSync(pkgFile).toString()) }
|
|
135
|
-
let info: any = {}
|
|
136
|
-
if (fs.existsSync(infoFile)) { info = JSON.parse(fs.readFileSync(infoFile).toString()) }
|
|
137
|
-
|
|
138
|
-
return info.title ?? pkg.name ?? 'API'
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function RemoveExistingVersions (client: APIGatewayClient): Promise<void> {
|
|
142
|
-
await new Promise(resolve => {
|
|
143
|
-
const ourName = findApiName()
|
|
144
|
-
// ClogDebug("looking for name "+ ourName)
|
|
145
|
-
const listCommand = new GetRestApisCommand({})
|
|
146
|
-
client.send(listCommand).then((response: any) => {
|
|
147
|
-
// ClogInfo(`There are ${response.items.length} other APIs`)
|
|
148
|
-
|
|
149
|
-
const DeleteMatchingApis = async (i: number = 0): Promise<any> => {
|
|
150
|
-
if (i >= response.items.length) {
|
|
151
|
-
resolve(undefined)
|
|
152
|
-
return await Promise.resolve(undefined)
|
|
153
|
-
}
|
|
154
|
-
const item: { name: string, id: string } = response.items[i]
|
|
155
|
-
if (item.name === ourName) {
|
|
156
|
-
// ClogInfo(`Found previous ${ourName}, [${item.id}] -- deleting...`)
|
|
157
|
-
const deleteCommand = new DeleteRestApiCommand({
|
|
158
|
-
restApiId: item.id
|
|
159
|
-
})
|
|
160
|
-
return await client.send(deleteCommand).then(() => {
|
|
161
|
-
delay(5000).then(async () => {
|
|
162
|
-
console.log(ac.magenta.bold(`Removed previous id ${item.id}`))
|
|
163
|
-
return await DeleteMatchingApis(++i)
|
|
164
|
-
}).catch<any>((reason: any) => undefined)
|
|
165
|
-
}).catch<any>((reason: any) => undefined)
|
|
166
|
-
} else {
|
|
167
|
-
return await DeleteMatchingApis(++i)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
DeleteMatchingApis().catch<any>((reason: any) => undefined)
|
|
171
|
-
}).catch<any>((reason: any) => undefined)
|
|
172
|
-
}).catch<any>((reason: any) => undefined)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
class FunctionInfo {
|
|
176
|
-
name: string = ''
|
|
177
|
-
arn: string = ''
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
class PrereqInfo {
|
|
181
|
-
requestApiId: string = ''
|
|
182
|
-
functions: FunctionInfo[] = []
|
|
183
|
-
apis: any[] = [] // Resource + method
|
|
184
|
-
defs: any[] = []
|
|
185
|
-
|
|
186
|
-
public constructor () {
|
|
187
|
-
this.functions = []
|
|
188
|
-
this.apis = []
|
|
189
|
-
this.defs = []
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
public findApi (pathMap: string, allowedMethods: string): any {
|
|
193
|
-
// find the pathmap in the apis list
|
|
194
|
-
// console.log("findApi "+pathMap, allowedMethods)
|
|
195
|
-
// see that this method exists in method map
|
|
196
|
-
for (const api of this.apis) {
|
|
197
|
-
if (api?.path === pathMap) {
|
|
198
|
-
// console.log("resourceMethods", api.resourceMethods)
|
|
199
|
-
const methodList = Object.getOwnPropertyNames(api.resourceMethods)
|
|
200
|
-
// ClogTrace("methodList", methodList)
|
|
201
|
-
for (const meth of allowedMethods.toUpperCase().split(',')) {
|
|
202
|
-
// console.log("meth", meth)
|
|
203
|
-
if (methodList.includes(meth)) {
|
|
204
|
-
(api).method = meth
|
|
205
|
-
return api
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return null
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
public findARN (name: string): string {
|
|
214
|
-
for (const f of this.functions ?? []) {
|
|
215
|
-
const lastus = f.name.lastIndexOf('_')
|
|
216
|
-
const fname = f.name.substring(0, lastus)
|
|
217
|
-
if (fname.toLowerCase() === name.toLowerCase()) return f.arn
|
|
218
|
-
}
|
|
219
|
-
console.log("$$$ Couldn't find " + name + ' in ', this.functions)
|
|
220
|
-
return ''
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
public MakeRequests (): PutIntegrationRequest[] {
|
|
224
|
-
const region = getSettings()?.awsPreferredRegion ?? ''
|
|
225
|
-
const out: PutIntegrationRequest[] = []
|
|
226
|
-
for (const d of this.defs) {
|
|
227
|
-
const def = (d)
|
|
228
|
-
const api = this.findApi(def.pathMap, def.allowedMethods)
|
|
229
|
-
const arn = this.findARN(def.name) ?? ''
|
|
230
|
-
if (arn === '') {
|
|
231
|
-
console.log(`>>> No ARN for ${(def.name as string)} ${(def.pathMap as string)}, ${(api.id as string)}`)
|
|
232
|
-
}
|
|
233
|
-
if (api !== undefined) {
|
|
234
|
-
out.push({
|
|
235
|
-
restApiId: this.requestApiId,
|
|
236
|
-
resourceId: api.id, // api.parentId
|
|
237
|
-
httpMethod: api.method,
|
|
238
|
-
integrationHttpMethod: 'POST', // api.method,
|
|
239
|
-
type: 'AWS_PROXY',
|
|
240
|
-
uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${arn}/invocations`,
|
|
241
|
-
credentials: 'arn:aws:iam::545650260286:role/TBDLambdaExecution2'
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return out
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function PrequisiteValues (id: string): Promise<PrereqInfo> {
|
|
250
|
-
const pri = new PrereqInfo()
|
|
251
|
-
const out = await GetFunctionInfo(pri)
|
|
252
|
-
out.defs = gatherFunctionDefinitions()
|
|
253
|
-
addBuiltInDefinitions(out.defs)
|
|
254
|
-
out.requestApiId = id
|
|
255
|
-
await GetMethodResources(id, out)
|
|
256
|
-
|
|
257
|
-
return out
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function GetFunctionInfo (info: PrereqInfo): Promise<PrereqInfo> {
|
|
261
|
-
const client = new LambdaClient(getAWSCredentials())
|
|
262
|
-
const listCommand: any = new ListFunctionsCommand({})
|
|
263
|
-
const response: any = await client.send(listCommand)
|
|
264
|
-
|
|
265
|
-
const sfx = '_' + md5((getProjectName() ?? '') + (getProjectVersion()?.toString() ?? ''))
|
|
266
|
-
|
|
267
|
-
for (const func of response.Functions ?? []) {
|
|
268
|
-
const fName: string = func?.FunctionName ?? ''
|
|
269
|
-
if (fName.endsWith(sfx)) {
|
|
270
|
-
info.functions.push({
|
|
271
|
-
name: func.FunctionName ?? '',
|
|
272
|
-
arn: func.FunctionArn ?? ''
|
|
273
|
-
})
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return info
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function GetMethodResources (
|
|
280
|
-
id: string,
|
|
281
|
-
info: PrereqInfo
|
|
282
|
-
): Promise<PrereqInfo> {
|
|
283
|
-
const client = new APIGatewayClient(getAWSCredentials())
|
|
284
|
-
const listCommand = new GetResourcesCommand({
|
|
285
|
-
restApiId: id
|
|
286
|
-
})
|
|
287
|
-
const response: GetResourcesCommandOutput = await client.send(listCommand)
|
|
288
|
-
// ClogInfo("Resources response", response)
|
|
289
|
-
if ((response?.items) != null) info.apis = response.items
|
|
290
|
-
|
|
291
|
-
return info
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async function PutIntegrations (integrations: PutIntegrationRequest[]): Promise<void> {
|
|
295
|
-
const client = new APIGatewayClient(getAWSCredentials())
|
|
296
|
-
for (const input of integrations) {
|
|
297
|
-
try {
|
|
298
|
-
const command = new PutIntegrationCommand(input)
|
|
299
|
-
/* const intResp = */ await client.send(command)
|
|
300
|
-
// ClogDebug("integration response", intResp);
|
|
301
|
-
await delay(5000)
|
|
302
|
-
} catch (e: any) {
|
|
303
|
-
console.error('Problem with integration for ' + (input.uri as string), { input })
|
|
304
|
-
throw e
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async function DeployApi (restApiId: string, stageName: string): Promise<void> {
|
|
310
|
-
try {
|
|
311
|
-
const client = new APIGatewayClient(getAWSCredentials())
|
|
312
|
-
const command = new CreateDeploymentCommand({
|
|
313
|
-
restApiId,
|
|
314
|
-
stageName
|
|
315
|
-
})
|
|
316
|
-
await client.send(command)
|
|
317
|
-
} catch (e: any) {
|
|
318
|
-
console.error(ac.red.bold(`Error with deployApi: ${e.message as string}`))
|
|
319
|
-
throw e
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function recordLatestPublish (publishUrl: string): void {
|
|
324
|
-
const publishRecord = {
|
|
325
|
-
url: publishUrl,
|
|
326
|
-
time: Date.now()
|
|
327
|
-
}
|
|
328
|
-
const publishedFile = path.join(projectPaths.basePath, '.published')
|
|
329
|
-
fs.writeFileSync(publishedFile, JSON.stringify(publishRecord))
|
|
330
|
-
}
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
LambdaClient,
|
|
4
|
+
ListFunctionsCommand
|
|
5
|
+
} from '@aws-sdk/client-lambda'
|
|
6
|
+
import {
|
|
7
|
+
APIGatewayClient,
|
|
8
|
+
ImportRestApiCommand,
|
|
9
|
+
GetRestApisCommand,
|
|
10
|
+
GetResourcesCommand, GetResourcesCommandOutput,
|
|
11
|
+
DeleteRestApiCommand,
|
|
12
|
+
PutIntegrationRequest,
|
|
13
|
+
PutIntegrationCommand,
|
|
14
|
+
CreateDeploymentCommand
|
|
15
|
+
} from '@aws-sdk/client-api-gateway'
|
|
16
|
+
|
|
17
|
+
import { getAWSCredentials, getSettings } from '../lib/LiftConfig'
|
|
18
|
+
|
|
19
|
+
import * as ac from 'ansi-colors'
|
|
20
|
+
|
|
21
|
+
import path from 'path'
|
|
22
|
+
import fs, { mkdirSync } from 'fs'
|
|
23
|
+
import os from 'os'
|
|
24
|
+
import { resolvePaths } from '../lib/pathResolve'
|
|
25
|
+
import { gatherFunctionDefinitions } from '../lib/openAPI/ApiBuildCollector'
|
|
26
|
+
import { delay } from '../lib/utils'
|
|
27
|
+
import { addBuiltInDefinitions, MakeBuiltinApiDoc } from './builtin/ApiDocMaker'
|
|
28
|
+
import { DeployApiBuiltin, DeployRootFileserves, DeployWebrootBuiltIn } from './builtin/BuiltInHandler'
|
|
29
|
+
import { getProjectName, getProjectVersion } from '../lib/LiftVersion'
|
|
30
|
+
import md5 from 'md5'
|
|
31
|
+
|
|
32
|
+
let projectPaths: any
|
|
33
|
+
|
|
34
|
+
export async function doPublishAsync (stageName?: string): Promise<number> {
|
|
35
|
+
// let retCode = await doDeployAsync([])
|
|
36
|
+
// if(retCode) return retCode
|
|
37
|
+
const retCode = 0
|
|
38
|
+
|
|
39
|
+
projectPaths = resolvePaths()
|
|
40
|
+
if (projectPaths.verified !== true) {
|
|
41
|
+
console.log(ac.bold.magenta('current directory is not at project root'))
|
|
42
|
+
return -1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await publishApi(stageName ?? 'Dev')
|
|
46
|
+
|
|
47
|
+
return retCode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function publishApi (
|
|
51
|
+
stageName?: string
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
if (stageName === undefined) stageName = 'Dev'
|
|
54
|
+
console.log(ac.green.bold(`Publishing Api to ${stageName}`))
|
|
55
|
+
|
|
56
|
+
// do the built-in deploys
|
|
57
|
+
await DeployWebrootBuiltIn()
|
|
58
|
+
await DeployRootFileserves()
|
|
59
|
+
await DeployApiBuiltin()
|
|
60
|
+
|
|
61
|
+
// make a private api
|
|
62
|
+
// const fs = require('fs')
|
|
63
|
+
// const os = require('os')
|
|
64
|
+
// const path = require('path')
|
|
65
|
+
|
|
66
|
+
let tmpDir
|
|
67
|
+
let apiBytes
|
|
68
|
+
const appPrefix = 'MistLift'
|
|
69
|
+
try {
|
|
70
|
+
tmpDir = path.join(os.tmpdir(), appPrefix)
|
|
71
|
+
// console.log("making "+tmpDir)
|
|
72
|
+
mkdirSync(tmpDir, { recursive: true })
|
|
73
|
+
|
|
74
|
+
const yamlFile = path.join(tmpDir, 'papi.yaml')
|
|
75
|
+
await MakeBuiltinApiDoc(yamlFile)
|
|
76
|
+
// console.log("reading "+yamlFile)
|
|
77
|
+
apiBytes = fs.readFileSync(yamlFile)
|
|
78
|
+
} catch (e: any) {
|
|
79
|
+
console.error(ac.red.bold('Error creating temp staging folder ') + (e.message as string))
|
|
80
|
+
throw Error()
|
|
81
|
+
} finally {
|
|
82
|
+
try {
|
|
83
|
+
if (tmpDir !== undefined) {
|
|
84
|
+
fs.rmSync(tmpDir, { recursive: true })
|
|
85
|
+
}
|
|
86
|
+
} catch (e: any) {
|
|
87
|
+
console.error(`An error has occurred while removing the temp folder at ${tmpDir ?? ''}. Please remove it manually. Error: ${(e.message as string)}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const client = new APIGatewayClient(getAWSCredentials())
|
|
92
|
+
|
|
93
|
+
await RemoveExistingVersions(client)
|
|
94
|
+
|
|
95
|
+
const command = new ImportRestApiCommand({
|
|
96
|
+
failOnWarnings: false,
|
|
97
|
+
body: apiBytes
|
|
98
|
+
})
|
|
99
|
+
let apiId
|
|
100
|
+
try {
|
|
101
|
+
const response = await client.send(command)
|
|
102
|
+
const { name, id, version, warnings } = response
|
|
103
|
+
console.log(ac.grey(`\n\nAPI ${name ?? ''} version ${version ?? ''} [${id ?? ''}] schema created`))
|
|
104
|
+
apiId = id ?? ''
|
|
105
|
+
if (warnings?.length !== 0) {
|
|
106
|
+
console.log(ac.magenta(` with ${warnings?.length ?? 0} warnings`))
|
|
107
|
+
if (warnings !== undefined) {
|
|
108
|
+
for (const warning of warnings) {
|
|
109
|
+
console.log(ac.grey.italic(` ${warning}`))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (e: any) {
|
|
114
|
+
console.error(ac.bold.red(e.message))
|
|
115
|
+
}
|
|
116
|
+
// const apidoc = new TextDecoder().decode(apiBytes)
|
|
117
|
+
// console.log(apidoc)
|
|
118
|
+
if (apiId === '') return
|
|
119
|
+
|
|
120
|
+
console.log(ac.grey('Continuing with binding...'))
|
|
121
|
+
const prereq = await PrequisiteValues(apiId ?? '')
|
|
122
|
+
const intRequests = prereq.MakeRequests()
|
|
123
|
+
await PutIntegrations(intRequests)
|
|
124
|
+
await DeployApi(apiId ?? '', stageName)
|
|
125
|
+
const region = getSettings()?.awsPreferredRegion ?? ''
|
|
126
|
+
const publishUrl = `https://${apiId ?? ''}.execute-api.${region}.amazonaws.com/${stageName}`
|
|
127
|
+
console.log(ac.green.bold(`\n Successfully deployed to ${publishUrl}`))
|
|
128
|
+
recordLatestPublish(publishUrl)
|
|
129
|
+
}
|
|
130
|
+
function findApiName (): string {
|
|
131
|
+
const infoFile = path.join(projectPaths.functionPath, 'apiService.info.json')
|
|
132
|
+
const pkgFile = projectPaths.packagePath
|
|
133
|
+
let pkg: any = {}
|
|
134
|
+
if (fs.existsSync(pkgFile)) { pkg = JSON.parse(fs.readFileSync(pkgFile).toString()) }
|
|
135
|
+
let info: any = {}
|
|
136
|
+
if (fs.existsSync(infoFile)) { info = JSON.parse(fs.readFileSync(infoFile).toString()) }
|
|
137
|
+
|
|
138
|
+
return info.title ?? pkg.name ?? 'API'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function RemoveExistingVersions (client: APIGatewayClient): Promise<void> {
|
|
142
|
+
await new Promise(resolve => {
|
|
143
|
+
const ourName = findApiName()
|
|
144
|
+
// ClogDebug("looking for name "+ ourName)
|
|
145
|
+
const listCommand = new GetRestApisCommand({})
|
|
146
|
+
client.send(listCommand).then((response: any) => {
|
|
147
|
+
// ClogInfo(`There are ${response.items.length} other APIs`)
|
|
148
|
+
|
|
149
|
+
const DeleteMatchingApis = async (i: number = 0): Promise<any> => {
|
|
150
|
+
if (i >= response.items.length) {
|
|
151
|
+
resolve(undefined)
|
|
152
|
+
return await Promise.resolve(undefined)
|
|
153
|
+
}
|
|
154
|
+
const item: { name: string, id: string } = response.items[i]
|
|
155
|
+
if (item.name === ourName) {
|
|
156
|
+
// ClogInfo(`Found previous ${ourName}, [${item.id}] -- deleting...`)
|
|
157
|
+
const deleteCommand = new DeleteRestApiCommand({
|
|
158
|
+
restApiId: item.id
|
|
159
|
+
})
|
|
160
|
+
return await client.send(deleteCommand).then(() => {
|
|
161
|
+
delay(5000).then(async () => {
|
|
162
|
+
console.log(ac.magenta.bold(`Removed previous id ${item.id}`))
|
|
163
|
+
return await DeleteMatchingApis(++i)
|
|
164
|
+
}).catch<any>((reason: any) => undefined)
|
|
165
|
+
}).catch<any>((reason: any) => undefined)
|
|
166
|
+
} else {
|
|
167
|
+
return await DeleteMatchingApis(++i)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
DeleteMatchingApis().catch<any>((reason: any) => undefined)
|
|
171
|
+
}).catch<any>((reason: any) => undefined)
|
|
172
|
+
}).catch<any>((reason: any) => undefined)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
class FunctionInfo {
|
|
176
|
+
name: string = ''
|
|
177
|
+
arn: string = ''
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class PrereqInfo {
|
|
181
|
+
requestApiId: string = ''
|
|
182
|
+
functions: FunctionInfo[] = []
|
|
183
|
+
apis: any[] = [] // Resource + method
|
|
184
|
+
defs: any[] = []
|
|
185
|
+
|
|
186
|
+
public constructor () {
|
|
187
|
+
this.functions = []
|
|
188
|
+
this.apis = []
|
|
189
|
+
this.defs = []
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public findApi (pathMap: string, allowedMethods: string): any {
|
|
193
|
+
// find the pathmap in the apis list
|
|
194
|
+
// console.log("findApi "+pathMap, allowedMethods)
|
|
195
|
+
// see that this method exists in method map
|
|
196
|
+
for (const api of this.apis) {
|
|
197
|
+
if (api?.path === pathMap) {
|
|
198
|
+
// console.log("resourceMethods", api.resourceMethods)
|
|
199
|
+
const methodList = Object.getOwnPropertyNames(api.resourceMethods)
|
|
200
|
+
// ClogTrace("methodList", methodList)
|
|
201
|
+
for (const meth of allowedMethods.toUpperCase().split(',')) {
|
|
202
|
+
// console.log("meth", meth)
|
|
203
|
+
if (methodList.includes(meth)) {
|
|
204
|
+
(api).method = meth
|
|
205
|
+
return api
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public findARN (name: string): string {
|
|
214
|
+
for (const f of this.functions ?? []) {
|
|
215
|
+
const lastus = f.name.lastIndexOf('_')
|
|
216
|
+
const fname = f.name.substring(0, lastus)
|
|
217
|
+
if (fname.toLowerCase() === name.toLowerCase()) return f.arn
|
|
218
|
+
}
|
|
219
|
+
console.log("$$$ Couldn't find " + name + ' in ', this.functions)
|
|
220
|
+
return ''
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public MakeRequests (): PutIntegrationRequest[] {
|
|
224
|
+
const region = getSettings()?.awsPreferredRegion ?? ''
|
|
225
|
+
const out: PutIntegrationRequest[] = []
|
|
226
|
+
for (const d of this.defs) {
|
|
227
|
+
const def = (d)
|
|
228
|
+
const api = this.findApi(def.pathMap, def.allowedMethods)
|
|
229
|
+
const arn = this.findARN(def.name) ?? ''
|
|
230
|
+
if (arn === '') {
|
|
231
|
+
console.log(`>>> No ARN for ${(def.name as string)} ${(def.pathMap as string)}, ${(api.id as string)}`)
|
|
232
|
+
}
|
|
233
|
+
if (api !== undefined) {
|
|
234
|
+
out.push({
|
|
235
|
+
restApiId: this.requestApiId,
|
|
236
|
+
resourceId: api.id, // api.parentId
|
|
237
|
+
httpMethod: api.method,
|
|
238
|
+
integrationHttpMethod: 'POST', // api.method,
|
|
239
|
+
type: 'AWS_PROXY',
|
|
240
|
+
uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${arn}/invocations`,
|
|
241
|
+
credentials: 'arn:aws:iam::545650260286:role/TBDLambdaExecution2'
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return out
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function PrequisiteValues (id: string): Promise<PrereqInfo> {
|
|
250
|
+
const pri = new PrereqInfo()
|
|
251
|
+
const out = await GetFunctionInfo(pri)
|
|
252
|
+
out.defs = gatherFunctionDefinitions()
|
|
253
|
+
addBuiltInDefinitions(out.defs)
|
|
254
|
+
out.requestApiId = id
|
|
255
|
+
await GetMethodResources(id, out)
|
|
256
|
+
|
|
257
|
+
return out
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function GetFunctionInfo (info: PrereqInfo): Promise<PrereqInfo> {
|
|
261
|
+
const client = new LambdaClient(getAWSCredentials())
|
|
262
|
+
const listCommand: any = new ListFunctionsCommand({})
|
|
263
|
+
const response: any = await client.send(listCommand)
|
|
264
|
+
|
|
265
|
+
const sfx = '_' + md5((getProjectName() ?? '') + (getProjectVersion()?.toString() ?? ''))
|
|
266
|
+
|
|
267
|
+
for (const func of response.Functions ?? []) {
|
|
268
|
+
const fName: string = func?.FunctionName ?? ''
|
|
269
|
+
if (fName.endsWith(sfx)) {
|
|
270
|
+
info.functions.push({
|
|
271
|
+
name: func.FunctionName ?? '',
|
|
272
|
+
arn: func.FunctionArn ?? ''
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return info
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function GetMethodResources (
|
|
280
|
+
id: string,
|
|
281
|
+
info: PrereqInfo
|
|
282
|
+
): Promise<PrereqInfo> {
|
|
283
|
+
const client = new APIGatewayClient(getAWSCredentials())
|
|
284
|
+
const listCommand = new GetResourcesCommand({
|
|
285
|
+
restApiId: id
|
|
286
|
+
})
|
|
287
|
+
const response: GetResourcesCommandOutput = await client.send(listCommand)
|
|
288
|
+
// ClogInfo("Resources response", response)
|
|
289
|
+
if ((response?.items) != null) info.apis = response.items
|
|
290
|
+
|
|
291
|
+
return info
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function PutIntegrations (integrations: PutIntegrationRequest[]): Promise<void> {
|
|
295
|
+
const client = new APIGatewayClient(getAWSCredentials())
|
|
296
|
+
for (const input of integrations) {
|
|
297
|
+
try {
|
|
298
|
+
const command = new PutIntegrationCommand(input)
|
|
299
|
+
/* const intResp = */ await client.send(command)
|
|
300
|
+
// ClogDebug("integration response", intResp);
|
|
301
|
+
await delay(5000)
|
|
302
|
+
} catch (e: any) {
|
|
303
|
+
console.error('Problem with integration for ' + (input.uri as string), { input })
|
|
304
|
+
throw e
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function DeployApi (restApiId: string, stageName: string): Promise<void> {
|
|
310
|
+
try {
|
|
311
|
+
const client = new APIGatewayClient(getAWSCredentials())
|
|
312
|
+
const command = new CreateDeploymentCommand({
|
|
313
|
+
restApiId,
|
|
314
|
+
stageName
|
|
315
|
+
})
|
|
316
|
+
await client.send(command)
|
|
317
|
+
} catch (e: any) {
|
|
318
|
+
console.error(ac.red.bold(`Error with deployApi: ${e.message as string}`))
|
|
319
|
+
throw e
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function recordLatestPublish (publishUrl: string): void {
|
|
324
|
+
const publishRecord = {
|
|
325
|
+
url: publishUrl,
|
|
326
|
+
time: Date.now()
|
|
327
|
+
}
|
|
328
|
+
const publishedFile = path.join(projectPaths.basePath, '.published')
|
|
329
|
+
fs.writeFileSync(publishedFile, JSON.stringify(publishRecord))
|
|
330
|
+
}
|