feat: add setup wizard in database init#8204
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a full database init workflow and related utilities, two TypeScript function templates (raw SQL and Drizzle ORM) for Netlify Functions, and a --template flag to functions:create to select bundled templates non-interactively. New modules include CLI entrypoint for database init, DB connection error handling, package installation helpers, package.json dependency checks, path/timestamp helpers, initialization SQL/Drizzle templates, and tests for the init workflow. Also adds a dev dependency (tmp-promise) and updates the database status and functions creation flows to use the new utilities. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
src/commands/database/database.ts (1)
109-115: Consider using the exportedDatabaseInitOptionstype fromdb-init.js.The inline type
{ yes?: boolean }works but is redundant sincedb-init.tsexportsDatabaseInitOptions. Using the exported type ensures the action's type stays in sync with the function signature.Note that line 4 imports
DatabaseInitOptionsfrom./legacy/db-init.js, which is for the legacy workflow. The newinitDatabasefrom./db-init.jshas its own exported type.♻️ Suggested refactor
+import type { DatabaseInitOptions } from './db-init.js' // ... - .action(async (options: { yes?: boolean }, command: BaseCommand) => { + .action(async (options: DatabaseInitOptions, command: BaseCommand) => { const { initDatabase } = await import('./db-init.js') await initDatabase(options, command) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/database/database.ts` around lines 109 - 115, Replace the inline action parameter type with the exported DatabaseInitOptions from the new db-init.js: import the type DatabaseInitOptions from './db-init.js' and change the action signature from (options: { yes?: boolean }, command: BaseCommand) to (options: DatabaseInitOptions, command: BaseCommand); ensure you do not accidentally use the legacy ./legacy/db-init.js type and keep the dynamic import of initDatabase as-is.functions-templates/typescript/database/package.json (1)
17-22: Move type/build-time dependencies todevDependencies.
@types/nodeandtypescriptare development-time dependencies and should be indevDependencies, notdependencies. This keeps the runtime footprint smaller.Also, consider pinning
@netlify/databaseto a specific version range instead oflatestfor reproducible builds.♻️ Suggested dependency structure
"license": "MIT", "dependencies": { - "@netlify/database": "latest", - "@netlify/functions": "^5.2.0", - "@types/node": "^22.0.0", - "typescript": "^4.5.5" + "@netlify/database": "^1.0.0", + "@netlify/functions": "^5.2.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.0.0" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@functions-templates/typescript/database/package.json` around lines 17 - 22, package.json currently lists build/time packages under "dependencies": move "@types/node" and "typescript" out of "dependencies" into "devDependencies" so they aren't installed at runtime; update the JSON to add a "devDependencies" object (if missing) and place those two packages there. Also replace the "@netlify/database": "latest" entry with a pinned semver range (for example a caret or exact version) to ensure reproducible builds, keeping the package name and version string present under "dependencies". Ensure the package keys and JSON structure remain valid.src/commands/database/util/init-data.ts (1)
41-51: Non-null assertion on environment variable in generated config.The generated
drizzle.config.tsusesprocess.env.NETLIFY_DATABASE_URL!with a non-null assertion. If the env var is unset whendrizzle-kitruns, this will cause a runtime error with a potentially confusing message. Consider adding a fallback or a clearer error.♻️ Suggested safer env access
export const drizzleConfigTs = (migrationsOutPath: string): string => `import { defineConfig } from 'drizzle-kit' +const databaseUrl = process.env.NETLIFY_DATABASE_URL +if (!databaseUrl) { + throw new Error('NETLIFY_DATABASE_URL environment variable is required. Run "netlify dev" first.') +} + export default defineConfig({ dialect: 'postgresql', schema: './db/schema.ts', out: '${migrationsOutPath}', dbCredentials: { - url: process.env.NETLIFY_DATABASE_URL!, + url: databaseUrl, }, }) `🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/database/util/init-data.ts` around lines 41 - 51, The generated drizzle config (drizzleConfigTs) currently uses a non-null assertion on process.env.NETLIFY_DATABASE_URL in the template for drizzle.config.ts; replace that unsafe `!` usage with a safer access pattern: either provide a sensible fallback (e.g. an empty string or a default connection string) or inject a runtime check that throws a clear, descriptive error if NETLIFY_DATABASE_URL is missing so drizzle-kit fails with a helpful message; update the template string returned by drizzleConfigTs (which uses migrationsOutPath) to reference the safer access/check instead of process.env.NETLIFY_DATABASE_URL!.tests/unit/commands/database/db-init.test.ts (1)
137-150: Defensive check for--nameargument extraction.If
--nameis missing or is the last argument,argv.indexOf('--name') + 1could access an undefined element or index 0 (if--nameis not found,indexOfreturns-1).🛡️ Suggested safer extraction
mockSpawnAsync.mockImplementation(async (_cmd: string, args: string[], options: { cwd?: string }) => { const argv = args if (argv.includes('drizzle-kit') && argv.includes('generate')) { const cwd = options.cwd ?? projectRoot() const migrationsDir = join(cwd, 'netlify', 'database', 'migrations') await fs.mkdir(migrationsDir, { recursive: true }) - const name = argv[argv.indexOf('--name') + 1] + const nameIdx = argv.indexOf('--name') + const name = nameIdx !== -1 && nameIdx + 1 < argv.length ? argv[nameIdx + 1] : 'unnamed' const prefix = formatTimestamp(new Date())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/commands/database/db-init.test.ts` around lines 137 - 150, The mocked spawn handler (mockSpawnAsync) extracts the migration name unsafely via argv[argv.indexOf('--name') + 1], which can be undefined if '--name' is missing or last; modify the extraction in the mock implementation to first find const nameIndex = argv.indexOf('--name') and then validate that nameIndex !== -1 and nameIndex < argv.length - 1 before reading argv[nameIndex + 1]; if validation fails, either throw a clear error or fall back to a safe default (e.g., 'unnamed_migration') so the test mock won't access an undefined element.src/commands/functions/functions-create.ts (1)
756-759: Remove the comment block per coding guidelines.The function name
resolveTemplateMetadataand its signature are self-explanatory. As per coding guidelines, avoid comments explaining what the code does; make the code clean and self-explanatory instead.♻️ Suggested fix
-// Scans `functions-templates/<lang>` for a template whose `.mjs` metadata -// `name` matches. Returns its `functionType` and the language folder it lives -// in, or null if nothing matches. Used to skip the funcType/language prompts -// when the user passes `--template`. const resolveTemplateMetadata = async (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/functions/functions-create.ts` around lines 756 - 759, Remove the explanatory comment block above the resolveTemplateMetadata function; the function signature and name are self-explanatory so delete the entire comment (the multi-line description starting with "Scans `functions-templates/<lang>`...") and leave only the function implementation to keep the file compliant with the coding guidelines.src/commands/database/db-init.ts (1)
276-278: Consider using the detected package manager fordrizzle-kit generate.The command is hardcoded to
npx drizzle-kit generate, but the user may be using yarn, pnpm, or bun. For consistency with the rest of the wizard, consider passingpm.remoteRunArgsand constructing the command dynamically.♻️ Suggested approach
Pass
pmtoprintNextStepsand construct the command usingpm.remoteRunArgs:-const printNextSteps = (orm: QueryStyle, withStarter: boolean): void => { +const printNextSteps = (orm: QueryStyle, withStarter: boolean, pm: PmInfo): void => { // ... } else if (orm === 'drizzle') { log(' • Define your tables in `db/schema.ts`, then generate a migration from them:') - log(` ${chalk.cyan('npx drizzle-kit generate')}`) + log(` ${chalk.cyan(`${pm.remoteRunArgs.join(' ')} drizzle-kit generate`)}`) }Then update the call site accordingly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/database/db-init.ts` around lines 276 - 278, Update printNextSteps to accept the package manager descriptor (pm) and use pm.remoteRunArgs to build the drizzle-kit generate command instead of hardcoding "npx drizzle-kit generate"; specifically, change the printNextSteps signature to accept pm, construct the command string like `${pm.remoteRunArgs.join(' ')} drizzle-kit generate` (or equivalent for shell formatting) and replace the hardcoded log call in the branch that currently logs `npx drizzle-kit generate` so it logs the dynamically built command; also update the call site(s) that invoke printNextSteps to pass the detected pm.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@functions-templates/typescript/database-drizzle/`{{name}}.mts:
- Around line 11-21: The catch block currently exposes internal error details by
returning the computed details (from error instanceof Error ? error.message :
String(error)) in the Response.json; instead, stop returning the raw error to
clients: log the full error server-side (e.g., console.error or your app logger)
inside the catch and return only a generic error payload from Response.json
(keep the existing user-facing message but remove the details field or replace
it with a non-sensitive token/message). Update the catch handling around
Response.json and the details variable so Response.json no longer contains
internal error.message while ensuring the error is still logged for debugging.
In `@functions-templates/typescript/database/`{{name}}.mts:
- Around line 9-18: The catch block currently returns raw error details via the
details variable to clients (see the catch block and Response.json call);
instead, log the full error server-side (using your logger or console.error) and
return a generic client-facing message (e.g., "Internal server error while
querying the database") in the Response.json payload; optionally make exposing
raw errors configurable (e.g., a DEBUG/EXPOSE_ERRORS flag) and only include
details when that flag is true to avoid leaking sensitive info.
In `@src/commands/database/db-init.ts`:
- Around line 139-143: The install logic is using prerelease tags; update the
package tag from "@beta" to "@latest" when adding Drizzle packages to the
toInstall array so stable releases are installed. Specifically, change the
strings built for DRIZZLE_ORM_PACKAGE and DRIZZLE_KIT_PACKAGE (where
toInstall.push({ pkg: `${DRIZZLE_ORM_PACKAGE}@beta` }) and toInstall.push({ pkg:
`${DRIZZLE_KIT_PACKAGE}@beta`, dev: true }) occur) to use "@latest"; keep the
existing hasDependency(projectRoot) checks and toInstall array usage unchanged.
---
Nitpick comments:
In `@functions-templates/typescript/database/package.json`:
- Around line 17-22: package.json currently lists build/time packages under
"dependencies": move "@types/node" and "typescript" out of "dependencies" into
"devDependencies" so they aren't installed at runtime; update the JSON to add a
"devDependencies" object (if missing) and place those two packages there. Also
replace the "@netlify/database": "latest" entry with a pinned semver range (for
example a caret or exact version) to ensure reproducible builds, keeping the
package name and version string present under "dependencies". Ensure the package
keys and JSON structure remain valid.
In `@src/commands/database/database.ts`:
- Around line 109-115: Replace the inline action parameter type with the
exported DatabaseInitOptions from the new db-init.js: import the type
DatabaseInitOptions from './db-init.js' and change the action signature from
(options: { yes?: boolean }, command: BaseCommand) to (options:
DatabaseInitOptions, command: BaseCommand); ensure you do not accidentally use
the legacy ./legacy/db-init.js type and keep the dynamic import of initDatabase
as-is.
In `@src/commands/database/db-init.ts`:
- Around line 276-278: Update printNextSteps to accept the package manager
descriptor (pm) and use pm.remoteRunArgs to build the drizzle-kit generate
command instead of hardcoding "npx drizzle-kit generate"; specifically, change
the printNextSteps signature to accept pm, construct the command string like
`${pm.remoteRunArgs.join(' ')} drizzle-kit generate` (or equivalent for shell
formatting) and replace the hardcoded log call in the branch that currently logs
`npx drizzle-kit generate` so it logs the dynamically built command; also update
the call site(s) that invoke printNextSteps to pass the detected pm.
In `@src/commands/database/util/init-data.ts`:
- Around line 41-51: The generated drizzle config (drizzleConfigTs) currently
uses a non-null assertion on process.env.NETLIFY_DATABASE_URL in the template
for drizzle.config.ts; replace that unsafe `!` usage with a safer access
pattern: either provide a sensible fallback (e.g. an empty string or a default
connection string) or inject a runtime check that throws a clear, descriptive
error if NETLIFY_DATABASE_URL is missing so drizzle-kit fails with a helpful
message; update the template string returned by drizzleConfigTs (which uses
migrationsOutPath) to reference the safer access/check instead of
process.env.NETLIFY_DATABASE_URL!.
In `@src/commands/functions/functions-create.ts`:
- Around line 756-759: Remove the explanatory comment block above the
resolveTemplateMetadata function; the function signature and name are
self-explanatory so delete the entire comment (the multi-line description
starting with "Scans `functions-templates/<lang>`...") and leave only the
function implementation to keep the file compliant with the coding guidelines.
In `@tests/unit/commands/database/db-init.test.ts`:
- Around line 137-150: The mocked spawn handler (mockSpawnAsync) extracts the
migration name unsafely via argv[argv.indexOf('--name') + 1], which can be
undefined if '--name' is missing or last; modify the extraction in the mock
implementation to first find const nameIndex = argv.indexOf('--name') and then
validate that nameIndex !== -1 and nameIndex < argv.length - 1 before reading
argv[nameIndex + 1]; if validation fails, either throw a clear error or fall
back to a safe default (e.g., 'unnamed_migration') so the test mock won't access
an undefined element.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 68c98598-1420-49e7-95cf-8420680979c5
⛔ Files ignored due to path filters (3)
functions-templates/typescript/database-drizzle/package-lock.jsonis excluded by!**/package-lock.jsonfunctions-templates/typescript/database/package-lock.jsonis excluded by!**/package-lock.jsonpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (19)
docs/commands/functions.mdfunctions-templates/typescript/database-drizzle/.netlify-function-template.mjsfunctions-templates/typescript/database-drizzle/package.jsonfunctions-templates/typescript/database-drizzle/{{name}}.mtsfunctions-templates/typescript/database/.netlify-function-template.mjsfunctions-templates/typescript/database/package.jsonfunctions-templates/typescript/database/{{name}}.mtspackage.jsonsrc/commands/database/database.tssrc/commands/database/db-init.tssrc/commands/database/db-status.tssrc/commands/database/util/db-connection.tssrc/commands/database/util/init-data.tssrc/commands/database/util/package-json.tssrc/commands/database/util/packages.tssrc/commands/database/util/paths.tssrc/commands/functions/functions-create.tssrc/commands/functions/functions.tstests/unit/commands/database/db-init.test.ts
| } catch (error) { | ||
| const details = error instanceof Error ? error.message : String(error) | ||
|
|
||
| return Response.json( | ||
| { | ||
| error: "Couldn't query the database. If you haven't set up the schema yet, run `netlify database init`.", | ||
| details, | ||
| }, | ||
| { status: 500 }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Same concern: error details exposed to clients.
This template has the same information leakage pattern as the raw SQL template. For consistency, consider applying the same fix to both templates.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@functions-templates/typescript/database-drizzle/`{{name}}.mts around lines 11
- 21, The catch block currently exposes internal error details by returning the
computed details (from error instanceof Error ? error.message : String(error))
in the Response.json; instead, stop returning the raw error to clients: log the
full error server-side (e.g., console.error or your app logger) inside the catch
and return only a generic error payload from Response.json (keep the existing
user-facing message but remove the details field or replace it with a
non-sensitive token/message). Update the catch handling around Response.json and
the details variable so Response.json no longer contains internal error.message
while ensuring the error is still logged for debugging.
| } catch (error) { | ||
| const details = error instanceof Error ? error.message : String(error) | ||
|
|
||
| return Response.json( | ||
| { | ||
| error: "Couldn't query the database. If you haven't set up the schema yet, run `netlify database init`.", | ||
| details, | ||
| }, | ||
| { status: 500 }, | ||
| ) |
There was a problem hiding this comment.
Consider limiting error details exposed to clients.
The details field exposes the raw error message to API consumers. In production, this could leak sensitive information such as connection strings, file paths, or internal stack traces. Consider logging the full error server-side and returning a generic message to clients, or making this behavior configurable.
🛡️ Suggested safer error response
} catch (error) {
- const details = error instanceof Error ? error.message : String(error)
+ // Log full error server-side for debugging
+ console.error('Database query failed:', error)
return Response.json(
{
error: "Couldn't query the database. If you haven't set up the schema yet, run `netlify database init`.",
- details,
},
{ status: 500 },
)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (error) { | |
| const details = error instanceof Error ? error.message : String(error) | |
| return Response.json( | |
| { | |
| error: "Couldn't query the database. If you haven't set up the schema yet, run `netlify database init`.", | |
| details, | |
| }, | |
| { status: 500 }, | |
| ) | |
| } catch (error) { | |
| // Log full error server-side for debugging | |
| console.error('Database query failed:', error) | |
| return Response.json( | |
| { | |
| error: "Couldn't query the database. If you haven't set up the schema yet, run `netlify database init`.", | |
| }, | |
| { status: 500 }, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@functions-templates/typescript/database/`{{name}}.mts around lines 9 - 18,
The catch block currently returns raw error details via the details variable to
clients (see the catch block and Response.json call); instead, log the full
error server-side (using your logger or console.error) and return a generic
client-facing message (e.g., "Internal server error while querying the
database") in the Response.json payload; optionally make exposing raw errors
configurable (e.g., a DEBUG/EXPOSE_ERRORS flag) and only include details when
that flag is true to avoid leaking sensitive info.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/commands/database/db-init.ts (1)
169-176: Consider checking the exit code fromdrizzle-kit generate.If
drizzle-kit generatefails (e.g., schema syntax error, missing dependencies), the function continues to create the seed migration and attempt to apply migrations. This could leave the project in an inconsistent state with a seed migration but no corresponding schema migration.♻️ Proposed fix to handle spawn failure
log('') info(`Running \`drizzle-kit generate --name ${STARTER_MIGRATION_NAME}\` against your schema...`) const [runner, ...runnerArgs] = pm.remoteRunArgs - await spawnAsync(runner, [...runnerArgs, 'drizzle-kit', 'generate', '--name', STARTER_MIGRATION_NAME], { + const exitCode = await spawnAsync(runner, [...runnerArgs, 'drizzle-kit', 'generate', '--name', STARTER_MIGRATION_NAME], { stdio: 'inherit', shell: true, cwd: projectRoot, }) + if (exitCode !== 0) { + throw new Error(`drizzle-kit generate failed with exit code ${exitCode}`) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/database/db-init.ts` around lines 169 - 176, The call to spawnAsync that runs `drizzle-kit generate` (using pm.remoteRunArgs, runner, runnerArgs, STARTER_MIGRATION_NAME, projectRoot) may fail and the code currently proceeds regardless; update the code to detect a non-zero exit or thrown error from spawnAsync and abort/throw before creating the seed migration or applying migrations. Concretely, either wrap the await spawnAsync(...) in a try/catch and rethrow/log and return on error, or check the returned result/exit code and stop the function when it indicates failure so subsequent steps (seed migration creation and migrate) do not run on a failed generate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/commands/database/db-init.ts`:
- Around line 169-176: The call to spawnAsync that runs `drizzle-kit generate`
(using pm.remoteRunArgs, runner, runnerArgs, STARTER_MIGRATION_NAME,
projectRoot) may fail and the code currently proceeds regardless; update the
code to detect a non-zero exit or thrown error from spawnAsync and abort/throw
before creating the seed migration or applying migrations. Concretely, either
wrap the await spawnAsync(...) in a try/catch and rethrow/log and return on
error, or check the returned result/exit code and stop the function when it
indicates failure so subsequent steps (seed migration creation and migrate) do
not run on a failed generate.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b6d5f3b1-6052-46bf-8dfc-69ddf18e3adb
📒 Files selected for processing (4)
src/commands/database/db-init.tssrc/commands/database/db-migration-new.tssrc/commands/database/util/timestamp.tstests/unit/commands/database/db-init.test.ts
✅ Files skipped from review due to trivial changes (1)
- src/commands/database/util/timestamp.ts
🤖 I have created a release *beep* *boop* --- ## [25.6.0](v25.5.0...v25.6.0) (2026-04-24) ### Features * add setup wizard in `database init` ([#8204](#8204)) ([768a7a8](768a7a8)) ### Bug Fixes * **deps:** update netlify packages ([#8202](#8202)) ([d32ea28](d32ea28)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: token-generator-app[bot] <82042599+token-generator-app[bot]@users.noreply.github.com> Co-authored-by: Eduardo Bouças <mail@eduardoboucas.com>
Adds a
netlify database initcommand.