1 |
|
2 | const childProcess = require('child_process')
|
3 | const { promisify } = require('util')
|
4 | const fs = require('fs')
|
5 | const path = require('path')
|
6 | const c = require('./colors')
|
7 |
|
8 | const exec = promisify(childProcess.exec)
|
9 | const copyFile = promisify(fs.copyFile)
|
10 | const mkdir = promisify(fs.mkdir)
|
11 | const stat = promisify(fs.stat)
|
12 |
|
13 | function debug(message, ...optionalParams) {
|
14 | if (process.env.DEBUG && process.env.DEBUG === 'prisma:postinstall') {
|
15 | console.log(message, ...optionalParams)
|
16 | }
|
17 | }
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function addPackageJSON(pth) {
|
23 | if (pth.endsWith('package.json')) return pth
|
24 | return path.join(pth, 'package.json')
|
25 | }
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | function findPackageRoot(startPath, limit = 10) {
|
34 | if (!startPath || !fs.existsSync(startPath)) return null
|
35 | let currentPath = startPath
|
36 |
|
37 | for (let i = 0; i < limit; i++) {
|
38 | const pkgPath = addPackageJSON(currentPath)
|
39 | if (fs.existsSync(pkgPath)) {
|
40 | try {
|
41 | const pkg = require(pkgPath)
|
42 | if (pkg.name && !['@prisma/cli', 'prisma'].includes(pkg.name)) {
|
43 | return pkgPath.replace('package.json', '')
|
44 | }
|
45 | } catch {}
|
46 | }
|
47 | currentPath = path.join(currentPath, '../')
|
48 | }
|
49 | return null
|
50 | }
|
51 |
|
52 | async function main() {
|
53 | if (process.env.INIT_CWD) {
|
54 | process.chdir(process.env.INIT_CWD)
|
55 |
|
56 | }
|
57 | await ensureEmptyDotPrisma()
|
58 |
|
59 | const localPath = getLocalPackagePath()
|
60 |
|
61 | const installedGlobally = localPath ? undefined : await isInstalledGlobally()
|
62 |
|
63 |
|
64 |
|
65 | process.env.PRISMA_GENERATE_IN_POSTINSTALL = 'true'
|
66 |
|
67 |
|
68 | const root = findPackageRoot(localPath)
|
69 |
|
70 | process.env.PRISMA_GENERATE_IN_POSTINSTALL = root ? root : 'true'
|
71 |
|
72 | debug({
|
73 | localPath,
|
74 | installedGlobally,
|
75 | init_cwd: process.env.INIT_CWD,
|
76 | PRISMA_GENERATE_IN_POSTINSTALL: process.env.PRISMA_GENERATE_IN_POSTINSTALL,
|
77 | })
|
78 | try {
|
79 | if (localPath) {
|
80 | await run('node', [
|
81 | localPath,
|
82 | 'generate',
|
83 | '--postinstall',
|
84 | doubleQuote(getPostInstallTrigger()),
|
85 | ])
|
86 | return
|
87 | }
|
88 | if (installedGlobally) {
|
89 | await run('prisma', [
|
90 | 'generate',
|
91 | '--postinstall',
|
92 | doubleQuote(getPostInstallTrigger()),
|
93 | ])
|
94 | return
|
95 | }
|
96 | } catch (e) {
|
97 |
|
98 | if (e && e !== 1) {
|
99 | console.error(e)
|
100 | }
|
101 | debug(e)
|
102 | }
|
103 |
|
104 | if (!localPath && !installedGlobally) {
|
105 | console.error(
|
106 | `${c.yellow(
|
107 | 'warning',
|
108 | )} In order to use "@prisma/client", please install Prisma CLI. You can install it with "npm add -D prisma".`,
|
109 | )
|
110 | }
|
111 | }
|
112 |
|
113 | function getLocalPackagePath() {
|
114 | try {
|
115 | const packagePath = require.resolve('prisma/package.json')
|
116 | if (packagePath) {
|
117 | return require.resolve('prisma')
|
118 | }
|
119 | } catch (e) {
|
120 |
|
121 | }
|
122 |
|
123 | try {
|
124 | const packagePath = require.resolve('@prisma/cli/package.json')
|
125 | if (packagePath) {
|
126 | return require.resolve('@prisma/cli')
|
127 | }
|
128 | } catch (e) {}
|
129 |
|
130 | return null
|
131 | }
|
132 |
|
133 | async function isInstalledGlobally() {
|
134 | try {
|
135 | const result = await exec('prisma -v')
|
136 | if (result.stdout.includes('@prisma/client')) {
|
137 | return true
|
138 | } else {
|
139 | console.error(`${c.yellow('warning')} You still have the ${c.bold(
|
140 | 'prisma',
|
141 | )} cli (Prisma 1) installed globally.
|
142 | Please uninstall it with either ${c.green('npm remove -g prisma')} or ${c.green(
|
143 | 'yarn global remove prisma',
|
144 | )}.`)
|
145 | }
|
146 | } catch (e) {
|
147 | return false
|
148 | }
|
149 | }
|
150 |
|
151 | if (!process.env.PRISMA_SKIP_POSTINSTALL_GENERATE) {
|
152 | main()
|
153 | .catch((e) => {
|
154 | if (e.stderr) {
|
155 | if (e.stderr.includes(`Can't find schema.prisma`)) {
|
156 | console.error(
|
157 | `${c.yellow('warning')} @prisma/client needs a ${c.bold(
|
158 | 'schema.prisma',
|
159 | )} to function, but couldn't find it.
|
160 | Please either create one manually or use ${c.bold('prisma init')}.
|
161 | Once you created it, run ${c.bold('prisma generate')}.
|
162 | To keep Prisma related things separate, we recommend creating it in a subfolder called ${c.underline(
|
163 | './prisma',
|
164 | )} like so: ${c.underline('./prisma/schema.prisma')}\n`,
|
165 | )
|
166 | } else {
|
167 | console.error(e.stderr)
|
168 | }
|
169 | } else {
|
170 | console.error(e)
|
171 | }
|
172 | process.exit(0)
|
173 | })
|
174 | .finally(() => {
|
175 | debug(`postinstall trigger: ${getPostInstallTrigger()}`)
|
176 | })
|
177 | }
|
178 |
|
179 | function run(cmd, params, cwd = process.cwd()) {
|
180 | const child = childProcess.spawn(cmd, params, {
|
181 | stdio: ['pipe', 'inherit', 'inherit'],
|
182 | cwd,
|
183 | })
|
184 |
|
185 | return new Promise((resolve, reject) => {
|
186 | child.on('close', () => {
|
187 | resolve()
|
188 | })
|
189 | child.on('exit', (code) => {
|
190 | if (code === 0) {
|
191 | resolve()
|
192 | } else {
|
193 | reject(code)
|
194 | }
|
195 | })
|
196 | child.on('error', () => {
|
197 | reject()
|
198 | })
|
199 | })
|
200 | }
|
201 |
|
202 | async function ensureEmptyDotPrisma() {
|
203 | try {
|
204 | const dotPrismaClientDir = path.join(__dirname, '../../../.prisma/client')
|
205 | await makeDir(dotPrismaClientDir)
|
206 | const defaultIndexJsPath = path.join(dotPrismaClientDir, 'index.js')
|
207 | const defaultIndexBrowserJSPath = path.join(
|
208 | dotPrismaClientDir,
|
209 | 'index-browser.js',
|
210 | )
|
211 | const defaultIndexDTSPath = path.join(dotPrismaClientDir, 'index.d.ts')
|
212 |
|
213 | if (!fs.existsSync(defaultIndexJsPath)) {
|
214 | await copyFile(
|
215 | path.join(__dirname, 'default-index.js'),
|
216 | defaultIndexJsPath,
|
217 | )
|
218 | }
|
219 | if (!fs.existsSync(defaultIndexBrowserJSPath)) {
|
220 | await copyFile(
|
221 | path.join(__dirname, 'default-index-browser.js'),
|
222 | defaultIndexBrowserJSPath,
|
223 | )
|
224 | }
|
225 |
|
226 | if (!fs.existsSync(defaultIndexDTSPath)) {
|
227 | await copyFile(
|
228 | path.join(__dirname, 'default-index.d.ts'),
|
229 | defaultIndexDTSPath,
|
230 | )
|
231 | }
|
232 | } catch (e) {
|
233 | console.error(e)
|
234 | }
|
235 | }
|
236 |
|
237 | async function makeDir(input) {
|
238 | const make = async (pth) => {
|
239 | try {
|
240 | await mkdir(pth)
|
241 |
|
242 | return pth
|
243 | } catch (error) {
|
244 | if (error.code === 'EPERM') {
|
245 | throw error
|
246 | }
|
247 |
|
248 | if (error.code === 'ENOENT') {
|
249 | if (path.dirname(pth) === pth) {
|
250 | throw new Error(`operation not permitted, mkdir '${pth}'`)
|
251 | }
|
252 |
|
253 | if (error.message.includes('null bytes')) {
|
254 | throw error
|
255 | }
|
256 |
|
257 | await make(path.dirname(pth))
|
258 |
|
259 | return make(pth)
|
260 | }
|
261 |
|
262 | try {
|
263 | const stats = await stat(pth)
|
264 | if (!stats.isDirectory()) {
|
265 | throw new Error('The path is not a directory')
|
266 | }
|
267 | } catch (_) {
|
268 | throw error
|
269 | }
|
270 |
|
271 | return pth
|
272 | }
|
273 | }
|
274 |
|
275 | return make(path.resolve(input))
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | function getPostInstallTrigger() {
|
286 | |
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | const maybe_npm_config_argv_string = process.env.npm_config_argv
|
311 |
|
312 | if (maybe_npm_config_argv_string === undefined) {
|
313 | return UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING
|
314 | }
|
315 |
|
316 | let npm_config_argv
|
317 | try {
|
318 | npm_config_argv = JSON.parse(maybe_npm_config_argv_string)
|
319 | } catch (e) {
|
320 | return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR}: ${maybe_npm_config_argv_string}`
|
321 | }
|
322 |
|
323 | if (typeof npm_config_argv !== 'object' || npm_config_argv === null) {
|
324 | return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR}: ${maybe_npm_config_argv_string}`
|
325 | }
|
326 |
|
327 | const npm_config_arv_original_arr = npm_config_argv.original
|
328 |
|
329 | if (!Array.isArray(npm_config_arv_original_arr)) {
|
330 | return `${UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR}: ${maybe_npm_config_argv_string}`
|
331 | }
|
332 |
|
333 | const npm_config_arv_original = npm_config_arv_original_arr
|
334 | .filter((arg) => arg !== '')
|
335 | .join(' ')
|
336 |
|
337 | const command =
|
338 | npm_config_arv_original === ''
|
339 | ? getPackageManagerName()
|
340 | : [getPackageManagerName(), npm_config_arv_original].join(' ')
|
341 |
|
342 | return command
|
343 | }
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | function doubleQuote(x) {
|
349 | return `"${x}"`
|
350 | }
|
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 | function getPackageManagerName() {
|
357 | const userAgent = process.env.npm_config_user_agent
|
358 | if (!userAgent) return 'MISSING_NPM_CONFIG_USER_AGENT'
|
359 |
|
360 | const name = parsePackageManagerName(userAgent)
|
361 | if (!name) return `UNKNOWN_NPM_CONFIG_USER_AGENT(${userAgent})`
|
362 |
|
363 | return name
|
364 | }
|
365 |
|
366 |
|
367 |
|
368 |
|
369 | function parsePackageManagerName(userAgent) {
|
370 | let packageManager = null
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 | if (userAgent) {
|
377 | const matchResult = userAgent.match(/^([^\/]+)\/.+/)
|
378 | if (matchResult) {
|
379 | packageManager = matchResult[1].trim()
|
380 | }
|
381 | }
|
382 |
|
383 | return packageManager
|
384 | }
|
385 |
|
386 |
|
387 | const UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING'
|
388 |
|
389 | const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR'
|
390 |
|
391 | const UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR = 'UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR'
|
392 |
|
393 |
|
394 |
|
395 | exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING = UNABLE_TO_FIND_POSTINSTALL_TRIGGER__ENVAR_MISSING
|
396 | exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR = UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_PARSE_ERROR
|
397 | exports.UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR = UNABLE_TO_FIND_POSTINSTALL_TRIGGER_JSON_SCHEMA_ERROR
|
398 | exports.getPostInstallTrigger = getPostInstallTrigger
|