-
Notifications
You must be signed in to change notification settings - Fork 21
/
prismaLayer.ts
151 lines (129 loc) · 5.45 KB
/
prismaLayer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { AssetHashType, IgnoreMode } from 'aws-cdk-lib'
import { Code, LayerVersion, LayerVersionProps, Runtime } from 'aws-cdk-lib/aws-lambda'
import { Construct } from 'constructs'
import crypto from 'crypto'
import { App } from 'sst/constructs'
import { RUNTIME } from 'stacks'
// modules to mark as "external" when bundling
// added to prismaModules
const PRISMA_LAYER_EXTERNAL = ['@prisma/engines', '@prisma/engines-version', '@prisma/internals']
type PrismaEngine = 'introspection-engine' | 'schema-engine' | 'prisma-fmt' | 'libquery_engine'
export interface PrismaLayerProps extends Omit<LayerVersionProps, 'code'> {
// e.g. 5.0.0
prismaVersion?: string
// some more modules to add to the layer
nodeModules?: string[]
// binary targets, e.g. "linux-arm64-openssl-3.0.x"
binaryTargets?: string[]
// prisma libs
prismaModules?: string[]
// engines to keep
prismaEngines?: PrismaEngine[]
}
/**
* Construct a lambda layer with Prisma libraries.
* Be sure to omit the prisma layer modules from your function bundles with the `externalModules` option.
* Include `environment` to point prisma at the right library location.
*
* @example
* ```ts
* const prismaLayer = new PrismaLayer(this, "PrismaLayer", {
* layerVersionName: `${id}-prisma`,
* removalPolicy: isProduction ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY,
* })
*
* // default lambda function options
* const functionOptions: FunctionOptions = {
* layers: [prismaLayer],
* environment: { ...prismaLayer.environment, DEBUG: "*" },
* bundling: {
* externalModules: prismaLayer.externalModules,
* },
* }
*/
export class PrismaLayer extends LayerVersion {
externalModules: string[]
environment: Record<string, string>
constructor(scope: Construct, id: string, props: PrismaLayerProps = {}) {
const { prismaVersion, prismaModules, ...rest } = props
const nodeModules = props.nodeModules || []
const app = App.of(scope) as App
const layerDir = '/asset-output/nodejs'
const nm = `${layerDir}/node_modules`
const engineDir = `${nm}/@prisma/engines`
const internalsDir = `${nm}/@prisma/internals`
const clientDir = `${nm}/@prisma/client`
// what are we asking npm to install?
// deps to npm install to the layer
const modulesToInstall = prismaModules || ['@prisma/client', '@prisma/engines']
const modulesToInstallWithVersion = prismaVersion
? modulesToInstall.map((dep) => `${dep}@${prismaVersion}`)
: modulesToInstall
const modulesToInstallArgs = modulesToInstallWithVersion.concat(nodeModules).join(' ')
// target architectures
const binaryTargets = props.binaryTargets || ['linux-arm64-openssl-3.0.x']
// delete engines not requested
const allEngines: PrismaEngine[] = ['introspection-engine', 'schema-engine', 'libquery_engine', 'prisma-fmt']
const prismaEngines = props.prismaEngines || ['libquery_engine']
const deleteEngineCmds = allEngines.filter((e) => !prismaEngines.includes(e)).map((e) => `rm -f ${engineDir}/${e}*`)
const createBundleCommand = [
// create asset bundle in docker
'bash',
'-c',
[
`echo "Installing ${modulesToInstallArgs}"`,
'mkdir -p /tmp/npm && pushd /tmp/npm && HOME=/tmp npm i --no-save --no-package-lock npm@latest && popd',
`mkdir -p ${layerDir}`,
// install PRISMA_DEPS
`cd ${layerDir} && HOME=/tmp /tmp/npm/node_modules/.bin/npm install --omit dev --omit peer --omit optional ${modulesToInstallArgs}`,
`echo "Installed ${modulesToInstallArgs}"`,
`ls -la ${engineDir}`,
// delete unneeded engines
...deleteEngineCmds,
// internals sux
`rm -f ${internalsDir}/dist/libquery_engine*`,
`rm -f ${internalsDir}/dist/get-generators/libquery_engine*`,
`rm -rf ${internalsDir}/dist/get-generators/engines`,
// get rid of some junk
`rm -rf ${engineDir}/download`,
`rm -rf ${clientDir}/generator-build`,
`rm -rf ${nm}/@prisma/engine-core/node_modules/@prisma/engines`,
`rm -rf ${nm}/prisma/build/public`,
`rm -rf ${nm}/prisma/prisma-client/src/__tests__`,
`rm -rf ${nm}/prisma/prisma-client/generator-build`,
`rm -rf ${nm}/@types`,
`rm -rf ${nm}/.prisma`,
].join(' && '),
]
// hash our parameters so we know when we need to rebuild
const bundleCommandHash = crypto.createHash('sha256')
bundleCommandHash.update(JSON.stringify(createBundleCommand))
const bundleCommandDigest = bundleCommandHash.digest('hex')
const binaryTarget = binaryTargets[0]
// bundle
const code = Code.fromAsset('.', {
// don't send all our files to docker (slow)
ignoreMode: IgnoreMode.GLOB,
exclude: ['*'],
// if our bundle commands (basically our "dockerfile") changes then rebuild the image
assetHashType: AssetHashType.CUSTOM,
assetHash: bundleCommandDigest,
bundling: {
environment: {
PRISMA_CLI_BINARY_TARGETS: binaryTarget,
},
image: RUNTIME.bundlingImage,
command: createBundleCommand,
},
})
super(scope, id, { ...rest, code })
// hint for prisma to find the engine
this.environment = app.local
? {}
: {
PRISMA_QUERY_ENGINE_LIBRARY: `/opt/nodejs/node_modules/@prisma/engines/libquery_engine-${binaryTarget}.so.node`,
}
// modules provided by layer
this.externalModules = [...new Set([...PRISMA_LAYER_EXTERNAL, ...nodeModules])]
}
}