create-t3-app
create-t3-app copied to clipboard
feat: `.env.local` type-safe generation and 1Password integration
Is your feature request related to a problem? Please describe.
When I’m working on a real project, I always need to set up local databases, generate sample data, and connect to 1Password to avoid storing secrets in plain text in a .env file. Generally, there are a lot of things I have to do before running next dev
. I find it extremely useful to write a custom dev.ts
script that handles everything for me and, eventually, runs next dev
.
Describe the solution you'd like to see
- Generate a
scripts/dev.ts
(see the example below) - Replace
next dev
withbun run scripts/dev.ts
- Delete
.env.example
dev.ts
example (with type-safe env generation thanks to satisfies typeof env
)
Note: I just quickly put together different pieces from different scripts/dev.ts
files I personally use, this is just an example:
#!/usr/bin/env bun
import { type env } from "~/env";
import { $ } from "bun";
// ⚠️ WARNING!
// Do not include any sensitive data in this file.
// The .env.local file is generated with mock values and is only used for development.
// No real secret keys or passwords should be used in this file.
const safeEnvObject = {
NODE_ENV: "development",
NEXTAUTH_URL: "http://localhost:3000",
NEXTAUTH_SECRET: await $`openssl rand -base64 32`.text(), // or `"0".repeat(44)` if you want to share the same secret across multiple projects
DISCORD_CLIENT_ID: "op://eng/nextjs-local-env/DISCORD_CLIENT_ID",
DISCORD_CLIENT_SECRET: "op://eng/nextjs-local-env/DISCORD_CLIENT_SECRET",
} satisfies typeof env;
await Bun.write(
".env.local",
Object.entries(safeEnvObject)
.map(([key, value]) => `${key}=${value}`)
.join("\n"),
);
// Here, if you need it, you can start docker containers for databases, create sample data, etc.
// Bun.spawn(
// [
// "docker",
// "run",
// "--name",
// "my-container-name",
// "-d",
// "--rm",
// "-p",
// "5432:5432",
// "postgres:16",
// ],
// {
// // You can view the logs by running `docker logs my-container-name`
// stdout: null, // Write to /dev/null
// stderr: "inherit", // Inherit from the parent process.
// },
// );
const using1Password = Object.values(safeEnvObject).some((value) =>
value.startsWith("op://"),
);
const cmds = ["next", "dev"];
if (using1Password) {
// Store your account ID in a file named ".1password" in the root of your project, ignoring it in .gitignore.
// List users and accounts set up on your device with `op account list`.
const account = (await Bun.file(".1password").text()).trim();
cmds.unshift("op", "run ", "--account", account, "--");
}
const process = Bun.spawn(cmds, {
stdout: "inherit",
stderr: "inherit",
});
console.log(`Started process with PID ${process.pid} for Next.js dev server`);
Describe alternate solutions
I understand this is a VERY opinionated solution and may not fit well for everyone, but I wanted to share it anyway. I'm open to hearing feedback!
Additional information
No response