create-t3-app icon indicating copy to clipboard operation
create-t3-app copied to clipboard

feat: `.env.local` type-safe generation and 1Password integration

Open simonecervini opened this issue 1 year ago • 0 comments

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 with bun 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

simonecervini avatar Jan 20 '24 10:01 simonecervini