@voxgig/build 3.1.0 → 4.1.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/build.ts +47 -480
- package/dist/build.d.ts +16 -3
- package/dist/build.js +39 -367
- package/dist/build.js.map +1 -1
- package/dist/env/lambda/generate.d.ts +13 -0
- package/dist/env/lambda/generate.js +86 -0
- package/dist/env/lambda/generate.js.map +1 -0
- package/dist/env/lambda/res_yml.d.ts +7 -0
- package/dist/env/lambda/res_yml.js +91 -0
- package/dist/env/lambda/res_yml.js.map +1 -0
- package/dist/env/lambda/srv_handler.d.ts +10 -0
- package/dist/env/lambda/srv_handler.js +101 -0
- package/dist/env/lambda/srv_handler.js.map +1 -0
- package/dist/env/lambda/srv_yml.d.ts +5 -0
- package/dist/env/lambda/srv_yml.js +165 -0
- package/dist/env/lambda/srv_yml.js.map +1 -0
- package/dist/shape/conf.d.ts +2 -2
- package/dist/shape/ent.d.ts +1 -1
- package/dist/shape/msg.d.ts +1 -1
- package/env/lambda/generate.ts +105 -0
- package/env/lambda/res_yml.ts +140 -0
- package/env/lambda/srv_handler.ts +132 -0
- package/env/lambda/srv_yml.ts +201 -0
- package/package.json +17 -7
- package/tm/lambda/res.queue.yml.frag +6 -0
- package/tm/lambda/res.role.yml.frag +35 -0
- package/tm/lambda/srv.yml.frag +6 -0
- package/tm/lambda/srv_handler.js.frag +17 -0
- package/tm/lambda/srv_handler.ts.frag +17 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* Copyright © 2022-2026 Voxgig Ltd, MIT License. */
|
|
2
|
+
|
|
3
|
+
// Lambda handler template: one handler source file per lambda service,
|
|
4
|
+
// bootstrapping Seneca via the env folder and delegating to gateway-lambda.
|
|
5
|
+
|
|
6
|
+
import { dive, get, pinify } from '@voxgig/util'
|
|
7
|
+
|
|
8
|
+
import { MsgMetaShape } from '../../shape/msg'
|
|
9
|
+
|
|
10
|
+
import { generate, loadFragment, renderFragment } from './generate'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Only create if does not exist
|
|
14
|
+
const srv_handler = async (model: any, spec: {
|
|
15
|
+
folder: string
|
|
16
|
+
start?: string
|
|
17
|
+
env?: {
|
|
18
|
+
folder: string
|
|
19
|
+
}
|
|
20
|
+
lang?: string
|
|
21
|
+
tm?: string
|
|
22
|
+
}) => {
|
|
23
|
+
let lang = spec.lang || 'js'
|
|
24
|
+
|
|
25
|
+
const frag = loadFragment('srv_handler.' + lang + '.frag', spec)
|
|
26
|
+
|
|
27
|
+
const files: { name: string, content: string }[] = []
|
|
28
|
+
|
|
29
|
+
Object
|
|
30
|
+
.entries(model.main.srv)
|
|
31
|
+
.filter((entry: any) => entry[1].env?.lambda)
|
|
32
|
+
.forEach((entry: any) => {
|
|
33
|
+
const name = entry[0]
|
|
34
|
+
const srv = entry[1]
|
|
35
|
+
|
|
36
|
+
if ('custom' === srv.env.lambda.kind) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let start = spec.start || 'setup'
|
|
41
|
+
let envFolder = spec.env?.folder || '../../../env/lambda'
|
|
42
|
+
|
|
43
|
+
let handler = 'handler'
|
|
44
|
+
let modify = ''
|
|
45
|
+
|
|
46
|
+
// if (!srv.api.web.active) {
|
|
47
|
+
// if (srv.on && 0 < Object.keys(srv.on).length) {
|
|
48
|
+
// handler = 'eventhandler'
|
|
49
|
+
// modify = `
|
|
50
|
+
// event = {
|
|
51
|
+
// ...event,
|
|
52
|
+
// // TODO: @voxgig/system? util needed to handle this dynamically
|
|
53
|
+
// seneca$: { msg: '${srv.on[Object.keys(srv.on)[0]].events[0].msg}' },
|
|
54
|
+
// }
|
|
55
|
+
// `
|
|
56
|
+
// }
|
|
57
|
+
// }
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
let prepare = ''
|
|
61
|
+
let complete = ''
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
dive(model.main.msg.aim[name], 128).map((entry: any) => {
|
|
65
|
+
let path = ['aim', name, ...entry[0]]
|
|
66
|
+
let msgMeta = MsgMetaShape(entry[1])
|
|
67
|
+
let pin = pinify(path)
|
|
68
|
+
if (msgMeta.transport?.queue?.active) {
|
|
69
|
+
complete += `
|
|
70
|
+
seneca.listen({type:'sqs',pin:'${pin}'})`
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
dive(model.main.srv[name].out, 128).map((entry: any) => {
|
|
76
|
+
let path = entry[0]
|
|
77
|
+
let msgMetaMaybe = get(model.main.msg, path)
|
|
78
|
+
// console.log(name, path, msgMetaMaybe)
|
|
79
|
+
if (msgMetaMaybe?.$) {
|
|
80
|
+
let msgMeta = MsgMetaShape(msgMetaMaybe?.$)
|
|
81
|
+
let pin = pinify(path)
|
|
82
|
+
|
|
83
|
+
if (msgMeta.transport?.queue?.active) {
|
|
84
|
+
complete += `
|
|
85
|
+
seneca.client({type:'sqs',pin:'${pin}'})`
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
let makeGatewayHandler = false
|
|
91
|
+
let onlist = model.main.srv[name].on || {}
|
|
92
|
+
Object.entries(onlist).map((onitem: any) => {
|
|
93
|
+
onitem[1].events.map((event: any) => {
|
|
94
|
+
if ('s3' === event.source) {
|
|
95
|
+
if (!makeGatewayHandler) {
|
|
96
|
+
complete += `
|
|
97
|
+
|
|
98
|
+
const makeGatewayHandler = seneca.export('s3-store/makeGatewayHandler')`
|
|
99
|
+
makeGatewayHandler = true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
complete += `
|
|
103
|
+
seneca
|
|
104
|
+
.act('sys:gateway,kind:lambda,add:hook,hook:handler', {
|
|
105
|
+
handler: makeGatewayHandler('${event.msg}') })`
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
const content = renderFragment(frag, {
|
|
112
|
+
envFolder,
|
|
113
|
+
start,
|
|
114
|
+
name,
|
|
115
|
+
complete,
|
|
116
|
+
modify,
|
|
117
|
+
prepare,
|
|
118
|
+
handler,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
files.push({ name: name + '.' + lang, content })
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
if (0 < files.length) {
|
|
125
|
+
await generate(spec.folder, files)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
srv_handler,
|
|
132
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/* Copyright © 2022-2026 Voxgig Ltd, MIT License. */
|
|
2
|
+
|
|
3
|
+
// gen/serverless/srv.yml template: one Serverless function definition per
|
|
4
|
+
// lambda-active service, derived from the model.
|
|
5
|
+
|
|
6
|
+
import Fs from 'fs'
|
|
7
|
+
import Path from 'path'
|
|
8
|
+
|
|
9
|
+
import { camelify } from '@voxgig/util'
|
|
10
|
+
|
|
11
|
+
import { CoreConfShape } from '../../shape/conf'
|
|
12
|
+
|
|
13
|
+
import { generate, empty, TM, loadFragment, renderFragment } from './generate'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const srv_yml = async (model: any, spec: {
|
|
17
|
+
folder: string
|
|
18
|
+
tm?: string
|
|
19
|
+
}) => {
|
|
20
|
+
|
|
21
|
+
const core = CoreConfShape(model.main.conf.core)
|
|
22
|
+
|
|
23
|
+
let appname = core.name
|
|
24
|
+
let AppName = camelify(appname)
|
|
25
|
+
|
|
26
|
+
const frag = loadFragment('srv.yml.frag', spec)
|
|
27
|
+
|
|
28
|
+
let srv_yml_prefix_path = Path.join(spec.folder, 'srv.prefix.yml')
|
|
29
|
+
let srv_yml_suffix_path = Path.join(spec.folder, 'srv.suffix.yml')
|
|
30
|
+
|
|
31
|
+
let prefixContent = Fs.existsSync(srv_yml_prefix_path) ?
|
|
32
|
+
Fs.readFileSync(srv_yml_prefix_path) : ''
|
|
33
|
+
let suffixContent = Fs.existsSync(srv_yml_suffix_path) ?
|
|
34
|
+
Fs.readFileSync(srv_yml_suffix_path) : ''
|
|
35
|
+
|
|
36
|
+
let content =
|
|
37
|
+
|
|
38
|
+
prefixContent +
|
|
39
|
+
Object
|
|
40
|
+
.entries(model.main.srv)
|
|
41
|
+
.filter((entry: any) => entry[1].env?.lambda?.active)
|
|
42
|
+
.map((entry: any) => {
|
|
43
|
+
const name = entry[0]
|
|
44
|
+
const srv = entry[1]
|
|
45
|
+
|
|
46
|
+
const lambda = srv.env.lambda
|
|
47
|
+
const handler = lambda.handler
|
|
48
|
+
|
|
49
|
+
// NOTE: gen.custom convention: allows for complete overwrite
|
|
50
|
+
// as a get-out-of-jail
|
|
51
|
+
if (srv.gen?.custom?.lambda?.srv_yml) {
|
|
52
|
+
return srv.gen.custom.lambda.srv_yml
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const web = srv.api.web
|
|
56
|
+
|
|
57
|
+
let events = ''
|
|
58
|
+
|
|
59
|
+
let onEvents = srv.on
|
|
60
|
+
|
|
61
|
+
if (onEvents) {
|
|
62
|
+
Object.entries(onEvents).forEach((entry: any[]) => {
|
|
63
|
+
// let name = entry[0]
|
|
64
|
+
let spec = entry[1]
|
|
65
|
+
|
|
66
|
+
if ('aws' === spec.provider) {
|
|
67
|
+
spec.events.forEach((ev: any) => {
|
|
68
|
+
if ('s3' === ev.source) {
|
|
69
|
+
events += TM(`
|
|
70
|
+
- s3:
|
|
71
|
+
bucket: ${ev.bucket}
|
|
72
|
+
event: ${ev.event}
|
|
73
|
+
existing: true
|
|
74
|
+
`)
|
|
75
|
+
if (ev.rules) {
|
|
76
|
+
events += TM(`
|
|
77
|
+
rules:
|
|
78
|
+
`)
|
|
79
|
+
if (ev.rules.prefix) {
|
|
80
|
+
events += TM(`
|
|
81
|
+
- prefix: ${ev.rules.prefix}
|
|
82
|
+
`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (ev.rules.suffix) {
|
|
86
|
+
events += TM(`
|
|
87
|
+
- suffix: ${ev.rules.suffix}
|
|
88
|
+
`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if ('schedule' === ev.source) {
|
|
93
|
+
let entries =
|
|
94
|
+
'string' === typeof ev.recur ? [ev.recur] : (ev.recur || [])
|
|
95
|
+
let recur = entries.map((entry: string) => {
|
|
96
|
+
let schedule = `
|
|
97
|
+
- schedule:
|
|
98
|
+
rate: ${entry}`
|
|
99
|
+
if (ev.msg) {
|
|
100
|
+
schedule += `
|
|
101
|
+
input:
|
|
102
|
+
msg: ${JSON.stringify(ev.msg)} `
|
|
103
|
+
}
|
|
104
|
+
return schedule
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
events += TM(`
|
|
108
|
+
${recur}
|
|
109
|
+
`)
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
else if ('sqs' === ev.source) {
|
|
113
|
+
events += TM(`
|
|
114
|
+
- sqs:
|
|
115
|
+
arn:
|
|
116
|
+
Fn::GetAtt:
|
|
117
|
+
- ${ev.qrn}
|
|
118
|
+
- Arn
|
|
119
|
+
batchSize: 1
|
|
120
|
+
`)
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// TODO: move to `on`
|
|
129
|
+
if (web.active) {
|
|
130
|
+
let prefix = web.path.prefix
|
|
131
|
+
let suffix = web.path.suffix
|
|
132
|
+
let area = web.path.area
|
|
133
|
+
let method = web.method
|
|
134
|
+
let corsflag = 'false'
|
|
135
|
+
let corsprops = ''
|
|
136
|
+
|
|
137
|
+
let methods = method.split(',')
|
|
138
|
+
// console.log('METHODS', methods)
|
|
139
|
+
|
|
140
|
+
if (web.cors.active) {
|
|
141
|
+
corsflag = 'true'
|
|
142
|
+
if (web.cors.props && !empty(web.cors.props)) {
|
|
143
|
+
corsflag = ''
|
|
144
|
+
corsprops = Object
|
|
145
|
+
.entries(web.cors.props)
|
|
146
|
+
.reduce(((a: any, nv: any) => (
|
|
147
|
+
a += ` ${nv[0]}: ${nv[1]}\n`
|
|
148
|
+
, a)), '')
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if ('v2' === web.lambda?.gateway) {
|
|
153
|
+
for (let method of methods) {
|
|
154
|
+
events += TM(`
|
|
155
|
+
- httpApi:
|
|
156
|
+
path: "${prefix}${area}${name}${suffix}"
|
|
157
|
+
method: ${method}
|
|
158
|
+
`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
for (let method of methods) {
|
|
163
|
+
events += TM(`
|
|
164
|
+
- http:
|
|
165
|
+
path: "${prefix}${area}${name}${suffix}"
|
|
166
|
+
method: ${method}
|
|
167
|
+
cors: ${corsflag}
|
|
168
|
+
${corsprops}
|
|
169
|
+
`)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
let eventsBlock = ''
|
|
177
|
+
if ('' !== events) {
|
|
178
|
+
eventsBlock = TM(`
|
|
179
|
+
events:
|
|
180
|
+
${events}
|
|
181
|
+
`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return renderFragment(frag, {
|
|
185
|
+
name,
|
|
186
|
+
handler: handler.path.prefix + name + handler.path.suffix,
|
|
187
|
+
AppName,
|
|
188
|
+
timeout: lambda.timeout,
|
|
189
|
+
memory: lambda.memory || 1024,
|
|
190
|
+
events: eventsBlock,
|
|
191
|
+
})
|
|
192
|
+
}).join('\n\n\n') +
|
|
193
|
+
suffixContent
|
|
194
|
+
|
|
195
|
+
await generate(spec.folder, [{ name: 'srv.yml', content }])
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
export {
|
|
200
|
+
srv_yml,
|
|
201
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voxgig/build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"main": "dist/build.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/build.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"url": "git://github.com/voxgig/build.git"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"model-s01": "voxgig-model test/s01/model/model.
|
|
21
|
+
"model-s01": "voxgig-model test/s01/model/model.aontu",
|
|
22
22
|
"test": "jest --coverage",
|
|
23
23
|
"test-some": "jest -t",
|
|
24
24
|
"test-watch": "jest --coverage --watchAll",
|
|
@@ -28,7 +28,13 @@
|
|
|
28
28
|
"reset": "npm run clean && npm i && npm run build && npm test",
|
|
29
29
|
"repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;",
|
|
30
30
|
"repo-publish": "npm run clean && npm i && npm run repo-publish-quick",
|
|
31
|
-
"repo-publish-quick": "npm run build && npm run test && npm run repo-tag && npm publish
|
|
31
|
+
"repo-publish-quick": "npm run build && npm run test && npm run repo-tag && npm run repo-stage-publish",
|
|
32
|
+
"repo-stage-publish": "aql vault exec --for=npm vxg:pub01 -- npm stage publish --registry https://registry.npmjs.org --access=public",
|
|
33
|
+
"repo-stage-publish-dry": "aql vault exec --dry-run --for=npm vxg:pub01 -- npm stage publish --registry https://registry.npmjs.org --access=public --dry-run",
|
|
34
|
+
"repo-stage-list": "aql vault exec --for=npm vxg:pub01 -- npm stage list",
|
|
35
|
+
"repo-stage-view": "aql vault exec --for=npm vxg:pub01 -- npm stage view",
|
|
36
|
+
"repo-stage-approve": "aql vault exec --for=npm vxg:pub01 -- npm stage approve",
|
|
37
|
+
"repo-stage-reject": "aql vault exec --for=npm vxg:pub01 -- npm stage reject"
|
|
32
38
|
},
|
|
33
39
|
"license": "MIT",
|
|
34
40
|
"files": [
|
|
@@ -36,17 +42,21 @@
|
|
|
36
42
|
"lib",
|
|
37
43
|
"dist",
|
|
38
44
|
"bin",
|
|
39
|
-
"LICENSE"
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"tm",
|
|
47
|
+
"env"
|
|
40
48
|
],
|
|
41
49
|
"dependencies": {
|
|
42
|
-
"
|
|
50
|
+
"@voxgig/util": "0.5.4",
|
|
51
|
+
"gubu": "8.0.2",
|
|
52
|
+
"jostraca": "0.31.2",
|
|
43
53
|
"js-yaml": "^4.1.0"
|
|
44
54
|
},
|
|
45
55
|
"devDependencies": {
|
|
46
56
|
"@types/jest": "^29.5.12",
|
|
47
57
|
"@types/js-yaml": "^4.0.9",
|
|
48
|
-
"@types/node": "
|
|
49
|
-
"@voxgig/model": "^2.
|
|
58
|
+
"@types/node": "26.1.2",
|
|
59
|
+
"@voxgig/model": "^9.2.4",
|
|
50
60
|
"@voxgig/system": "^1.2.0",
|
|
51
61
|
"esbuild": "^0.23.0",
|
|
52
62
|
"jest": "^29.7.0",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
Basic$$AppName$$LambdaRole:
|
|
3
|
+
Type: AWS::IAM::Role
|
|
4
|
+
Properties:
|
|
5
|
+
RoleName: Basic$$AppName$$LambdaRole${self:custom.index.BasicLambdaRole,"01"}
|
|
6
|
+
AssumeRolePolicyDocument:
|
|
7
|
+
Version: '2012-10-17'
|
|
8
|
+
Statement:
|
|
9
|
+
- Effect: Allow
|
|
10
|
+
Principal:
|
|
11
|
+
Service:
|
|
12
|
+
- lambda.amazonaws.com
|
|
13
|
+
- events.amazonaws.com
|
|
14
|
+
- ecs-tasks.amazonaws.com
|
|
15
|
+
Action: sts:AssumeRole
|
|
16
|
+
Policies:
|
|
17
|
+
- PolicyName: LambdaServiceAccess
|
|
18
|
+
PolicyDocument:
|
|
19
|
+
Version: '2012-10-17'
|
|
20
|
+
Statement:
|
|
21
|
+
- Effect: Allow
|
|
22
|
+
Action:
|
|
23
|
+
- dynamodb:DescribeTable
|
|
24
|
+
- dynamodb:GetItem
|
|
25
|
+
- dynamodb:PutItem
|
|
26
|
+
- dynamodb:UpdateItem
|
|
27
|
+
- dynamodb:DeleteItem
|
|
28
|
+
- dynamodb:Query
|
|
29
|
+
- dynamodb:Scan
|
|
30
|
+
Resource:
|
|
31
|
+
$$dynamoArns$$
|
|
32
|
+
$$customPolicy$$
|
|
33
|
+
ManagedPolicyArns:
|
|
34
|
+
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
|
35
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const getSeneca = require('$$envFolder$$/$$start$$')
|
|
2
|
+
|
|
3
|
+
function complete(seneca: any) {$$complete$$
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
exports.handler = async (
|
|
7
|
+
event,
|
|
8
|
+
context
|
|
9
|
+
) => {
|
|
10
|
+
$$modify$$
|
|
11
|
+
let seneca = await getSeneca('$$name$$', complete)
|
|
12
|
+
$$prepare$$
|
|
13
|
+
let handler = seneca.export('gateway-lambda/$$handler$$')
|
|
14
|
+
let res = await handler(event, context)
|
|
15
|
+
return res
|
|
16
|
+
}
|
|
17
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getSeneca } from '$$envFolder$$/$$start$$'
|
|
2
|
+
|
|
3
|
+
function complete(seneca: any) {$$complete$$
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
exports.handler = async (
|
|
7
|
+
event:any,
|
|
8
|
+
context:any
|
|
9
|
+
) => {
|
|
10
|
+
$$modify$$
|
|
11
|
+
let seneca = await getSeneca('$$name$$', complete)
|
|
12
|
+
$$prepare$$
|
|
13
|
+
let handler = seneca.export('gateway-lambda/$$handler$$')
|
|
14
|
+
let res = await handler(event, context)
|
|
15
|
+
return res
|
|
16
|
+
}
|
|
17
|
+
|