Hi there!
I’m a bit bummed by the fact that I have to live with an uncomittable change in my schema.prisma
file so that I don’t mess up the datasource provider in production…
I’m all for dev environment as close to production as possible, but for quick prototypes / small projects it’s nice to work with SQLite.
So I’ve been playing around with a hack tonight, and because it’s really hacky I’m hesitant to open a PR. Also, I don’t know if you have any information about Prisma allowing the use of environment variables for datasources provider
value? Might not be worth hacking around it if they’re close to releasing something.
Anyway, here’s the idea I’ve been toying with:
- added a module in
@redwood/cli
:
// packages/cli/src/lib/expand-schema-env.js
import fs from 'fs'
import path from 'path'
import { getPaths } from 'src/lib'
export const expandSchemaUnsupportedEnvVariables = () => {
// Get Prisma's schema file content
const prismaSchemaPath = path.join(
getPaths().base,
'api/prisma/schema.prisma'
)
const schemaOriginalContents = fs.readFileSync(prismaSchemaPath, 'utf-8')
let expandedSchema = schemaOriginalContents
// Find calls to `env()` in datasources & generator provider values
let matches = expandedSchema.matchAll(
/(?<lead>(?:datasource|generator)[^}]+provider[^=]*=\W+)(?<env_call>env\(["'](?<variable>[^"']*)["']\))/gm
)
matches = Array.from(matches).reverse()
for (const match of matches) {
// Replace each match with the actual environment value
expandedSchema =
expandedSchema.slice(0, match.index + match.groups.lead.length) +
'"' +
process.env[match.groups.variable] +
'"' +
expandedSchema.slice(match.index + match[0].length)
}
// Write newly expanded content to the schema file
fs.writeFileSync(prismaSchemaPath, expandedSchema)
return () => {
fs.writeFileSync(prismaSchemaPath, schemaOriginalContents)
}
}
- Updated commands in
packages/cli/src/commands/dbCommands
to rewrite the schema file right before the execution of the command, and restore it right after.
For example, thesave
command’s handler now looks like this:
export const handler = async ({ name, verbose = true }) => {
const restoreSchema = expandSchemaUnsupportedEnvVariables()
await runCommandTask(
[
{
title: 'Creating database migration...',
cmd: 'yarn prisma2',
args: [
'migrate save',
name && `--name ${name}`,
'--experimental',
].filter(Boolean),
},
],
{
verbose,
}
).finally(restoreSchema)
}
-
Updated my
.env.defaults
file to addDATABASE_PROVIDER=sqlite
-
Updated my schema’s datasource:
datasource DS {
provider = env("DATABASE_PROVIDER")
url = env("DATABASE_URL")
}
This works like a charm locally! (as long as you use env variables that do exist )
And because deploying only involves calling yarn rw db up --no-db-client && yarn rw build
and publishing the built files, I have no reason to believe it won’t work on Netlify or elsewhere.
A few notes:
-
Doing this means generated migrations will have a
schema.prisma
snapshot file that containsprovider = "sqlite"
and not theenv("DATABASE_PROVIDER")
call. But that’s actually already the case in the current state of things: migrations generated locally contain the same provider and are not preventing things to work with postgres once deployed.
I guess the snapshotted file is just here as a reference. -
yarn rw dev
doesn’t work anymore with that schema because it doesn’t delegate generation to the CLI commands but directly callsyarn prisma2 generate --watch
. I haven’t pushed the experiment further because I don’t want to spend more time on something that might be deemed completely useless
However I think we could make it work by introducing an intermediate watching layer: instead of having Prisma watch the file, we watch it ourselves, expand theenv()
calls from the file into a temporary file upon detected changes and callyarn prisma2 generate --schema=/path/to/schema.expanded.prisma
.
This way, the original file always looks “clean” to the developer, and the overall devX is improved.
… As I said it’s very hacky but it’s also future-proof: once Prisma catches up with this, just remove the expansion calls from the tooling and nothing will have to change on the developers’ side!
So, there you go… wdyt? ^^