Skip to content

Commit 059800f

Browse files
Merge pull request #3772 from laststance/fix/prisma-socket-migrate-url
fix: normalize prisma socket url for migrations
2 parents 520beda + 8a48708 commit 059800f

4 files changed

Lines changed: 97 additions & 5 deletions

File tree

.env.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ SENTRY_TRACES_SAMPLE_RATE="0.1"
1616
LOG_LEVEL="info"
1717
# Dev (compose.dev publishes 3306): TCP
1818
DATABASE_URL="mysql://root:your_mysql_root_password@127.0.0.1:3306/digital"
19-
# Production (compose.prod, PM2 on host): no TCP; use socketPath (URL-encode path if needed)
19+
# Production runtime (compose.prod, PM2 on host): no TCP; Prisma CLI normalizes socketPath to socket.
2020
# DATABASE_URL="mysql://root:your_mysql_root_password@localhost/digital?socketPath=/path/to/nsx/run/mysqld/mysqld.sock"
2121

2222
# Docker Compose (auto-read from .env by docker compose)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pm2 ps -a // Show all processes
132132
1. `source ~/.bashrc` && `volta install node`
133133
1. `pnpm`
134134
1. install docker on Ubuntu https://docs.docker.com/engine/install/ubuntu/#set-up-the-repository
135-
1. `docker compose -f compose.yml -f compose.prod.yml up -d` (MySQL has no host port; PM2 uses `DATABASE_URL` with `socketPath` — see `.env.sample`)
135+
1. `docker compose -f compose.yml -f compose.prod.yml up -d` (MySQL has no host port; PM2 runtime uses `DATABASE_URL` with `socketPath`, and Prisma CLI normalizes it for migrations — see `.env.sample`)
136136
1. pnpm db:migrate
137137
1. touch .env.prod
138138
1. npm i -g pm2

prisma.config.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,55 @@
11
import 'dotenv/config'
22
import { defineConfig } from 'prisma/config'
33

4+
const PLACEHOLDER_DATABASE_URL =
5+
'mysql://placeholder:placeholder@localhost:3306/placeholder'
6+
const RUNTIME_SOCKET_QUERY_PARAMETER = 'socketPath'
7+
const PRISMA_CLI_SOCKET_QUERY_PARAMETER = 'socket'
8+
9+
/**
10+
* Converts the app runtime MySQL socket URL into the Prisma CLI socket URL.
11+
* @param databaseUrl - DATABASE_URL read from the deploy environment.
12+
* @returns
13+
* - With socketPath: equivalent URL using Prisma's documented socket parameter
14+
* - Without socketPath: the original URL
15+
* - When missing: placeholder URL used only for schema parsing
16+
* @example
17+
* normalizeDatabaseUrlForPrismaCli('mysql://root:pass@localhost/digital?socketPath=/tmp/mysql.sock')
18+
* // => 'mysql://root:pass@localhost/digital?socket=%2Ftmp%2Fmysql.sock'
19+
*/
20+
export const normalizeDatabaseUrlForPrismaCli = (
21+
databaseUrl: string | undefined,
22+
): string => {
23+
if (!databaseUrl) {
24+
return PLACEHOLDER_DATABASE_URL
25+
}
26+
27+
try {
28+
const parsedDatabaseUrl = new URL(databaseUrl)
29+
const socketPath = parsedDatabaseUrl.searchParams.get(
30+
RUNTIME_SOCKET_QUERY_PARAMETER,
31+
)
32+
33+
if (!socketPath) {
34+
return databaseUrl
35+
}
36+
37+
// Prisma CLI expects `socket`, while the runtime MariaDB adapter uses `socketPath`.
38+
parsedDatabaseUrl.searchParams.delete(RUNTIME_SOCKET_QUERY_PARAMETER)
39+
parsedDatabaseUrl.searchParams.set(
40+
PRISMA_CLI_SOCKET_QUERY_PARAMETER,
41+
socketPath,
42+
)
43+
return parsedDatabaseUrl.toString()
44+
} catch {
45+
// Invalid URLs should still surface through Prisma; only translate the known key.
46+
return databaseUrl.replace(
47+
`${RUNTIME_SOCKET_QUERY_PARAMETER}=`,
48+
`${PRISMA_CLI_SOCKET_QUERY_PARAMETER}=`,
49+
)
50+
}
51+
}
52+
453
/**
554
* Prisma v7 configuration.
655
*
@@ -17,8 +66,6 @@ export default defineConfig({
1766
seed: 'tsx prisma/seed.ts',
1867
},
1968
datasource: {
20-
url:
21-
process.env.DATABASE_URL ||
22-
'mysql://placeholder:placeholder@localhost:3306/placeholder',
69+
url: normalizeDatabaseUrlForPrismaCli(process.env.DATABASE_URL),
2370
},
2471
})

src/lib/prismaConfig.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { normalizeDatabaseUrlForPrismaCli } from '../../prisma.config'
4+
5+
describe('normalizeDatabaseUrlForPrismaCli', () => {
6+
it('uses Prisma socket query when the production runtime URL contains socketPath', () => {
7+
// Arrange
8+
const databaseUrl =
9+
'mysql://root:secret@localhost/digital?socketPath=/home/deploy/nsx/run/mysqld/mysqld.sock'
10+
11+
// Act
12+
const normalizedDatabaseUrl = normalizeDatabaseUrlForPrismaCli(databaseUrl)
13+
14+
// Assert
15+
expect(normalizedDatabaseUrl).toBe(
16+
'mysql://root:secret@localhost/digital?socket=%2Fhome%2Fdeploy%2Fnsx%2Frun%2Fmysqld%2Fmysqld.sock',
17+
)
18+
})
19+
20+
it('keeps a TCP database URL unchanged when no socketPath query exists', () => {
21+
// Arrange
22+
const databaseUrl = 'mysql://root:secret@127.0.0.1:3306/digital'
23+
24+
// Act
25+
const normalizedDatabaseUrl = normalizeDatabaseUrlForPrismaCli(databaseUrl)
26+
27+
// Assert
28+
expect(normalizedDatabaseUrl).toBe(
29+
'mysql://root:secret@127.0.0.1:3306/digital',
30+
)
31+
})
32+
33+
it('uses the schema-parse placeholder when DATABASE_URL is missing', () => {
34+
// Arrange
35+
const databaseUrl = undefined
36+
37+
// Act
38+
const normalizedDatabaseUrl = normalizeDatabaseUrlForPrismaCli(databaseUrl)
39+
40+
// Assert
41+
expect(normalizedDatabaseUrl).toBe(
42+
'mysql://placeholder:placeholder@localhost:3306/placeholder',
43+
)
44+
})
45+
})

0 commit comments

Comments
 (0)