From da7dfa08ed7ba121acd2b001552c56d84fcb40b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 21 May 2024 00:23:47 +0200 Subject: [PATCH 01/11] feat: dev-env scripts --- README.md | 4 +- apps/nextjs/package.json | 4 +- apps/nextjs/scripts/write-dev-env.js | 17 +++++ packages/auth/README.md | 9 +++ packages/auth/package.json | 7 +- packages/auth/scripts/dev-env.js | 58 ++++++++++++++++ packages/auth/tsconfig.json | 2 +- packages/scripts/eslint.config.js | 10 +++ packages/scripts/package.json | 33 +++++++++ packages/scripts/src/dev-env.js | 100 +++++++++++++++++++++++++++ packages/scripts/src/http.js | 40 +++++++++++ packages/scripts/src/index.js | 1 + packages/scripts/tsconfig.json | 8 +++ pnpm-lock.yaml | 39 +++++++++-- 14 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 apps/nextjs/scripts/write-dev-env.js create mode 100644 packages/auth/README.md create mode 100644 packages/auth/scripts/dev-env.js create mode 100644 packages/scripts/eslint.config.js create mode 100644 packages/scripts/package.json create mode 100644 packages/scripts/src/dev-env.js create mode 100644 packages/scripts/src/http.js create mode 100644 packages/scripts/src/index.js create mode 100644 packages/scripts/tsconfig.json diff --git a/README.md b/README.md index d2ede62b0..4fba769ce 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,9 @@ To add a new package, simply run `pnpm turbo gen init` in the monorepo root. Thi The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package. -### 4. Configuring Next-Auth to work with Expo +### 4. Ensuring Next-Auth works with Expo locally -In order for the CSRF protection to work when developing locally, you will need to set the AUTH_URL to the same IP address your expo dev server is listening on. This address is displayed in your Expo CLI when starting the dev server. +Though everything should work out of the box, there's some auto-infering being done that may not work in all cases. Take a look at the [auth package README.md](./packages/auth/README.md) if you encounter any issue. ## FAQ diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 7b91bf6c5..8d96b4c6f 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -6,11 +6,12 @@ "scripts": { "build": "pnpm with-env next build", "clean": "git clean -xdf .next .turbo node_modules", - "dev": "pnpm with-env next dev", + "dev": "pnpm with-dev-env next dev", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", "start": "pnpm with-env next start", "typecheck": "tsc --noEmit", + "with-dev-env": "node ./scripts/write-dev-env.js && pnpm with-env", "with-env": "dotenv -e ../../.env --" }, "dependencies": { @@ -34,6 +35,7 @@ "devDependencies": { "@acme/eslint-config": "workspace:*", "@acme/prettier-config": "workspace:*", + "@acme/scripts": "workspace:*", "@acme/tailwind-config": "workspace:*", "@acme/tsconfig": "workspace:*", "@types/node": "^20.12.9", diff --git a/apps/nextjs/scripts/write-dev-env.js b/apps/nextjs/scripts/write-dev-env.js new file mode 100644 index 000000000..06e06b237 --- /dev/null +++ b/apps/nextjs/scripts/write-dev-env.js @@ -0,0 +1,17 @@ +// @ts-check +import * as auth from "@acme/auth/scripts/dev-env.js"; +import { addDevEnvToFile } from "@acme/scripts/dev-env.js"; + +const filePath = ".env.local"; +const devEnv = { + ...(await auth.getDevEnv()), +}; + +addDevEnvToFile({ + filePath, + devEnv, + source: "/apps/nextjs/scripts/write-dev-env.js", +}).catch((err) => { + console.error(`Failed to write dev env variables to ${filePath}`, err); + process.exit(1); +}); diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 000000000..7903b5c5e --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1,9 @@ +# packages/auth + +## FAQ + +### What's going on with `AUTH_URL` when running `pnpm dev`? + +In order for certain auth features to work when developing locally (CSRF protection and redirects), the `AUTH_URL` needs to point to the NextJS app but through the IP address your expo dev server is listening on (the address is displayed in your Expo CLI when starting the dev server). + +The `pnpm dev` command will try to infer the URL automatically, but it may not always work and you may get a different IP address and a different port. If that happens, you can always set it manually in your `.env` file or by setting it before the command, such as `AUTH_URL=http://x.x.x.x:x pnpm dev`. diff --git a/packages/auth/package.json b/packages/auth/package.json index c3216212f..40e97b148 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -8,7 +8,10 @@ "react-server": "./src/index.rsc.ts", "default": "./src/index.ts" }, - "./env": "./env.ts" + "./env": "./env.ts", + "./scripts/dev-env.js": { + "default": "./scripts/dev-env.js" + } }, "license": "MIT", "scripts": { @@ -19,6 +22,7 @@ }, "dependencies": { "@acme/db": "workspace:*", + "@acme/scripts": "workspace:*", "@auth/drizzle-adapter": "^1.1.0", "@t3-oss/env-nextjs": "^0.10.1", "next": "^14.2.3", @@ -30,6 +34,7 @@ "devDependencies": { "@acme/eslint-config": "workspace:*", "@acme/prettier-config": "workspace:*", + "@acme/scripts": "workspace:*", "@acme/tsconfig": "workspace:*", "eslint": "^9.2.0", "prettier": "^3.2.5", diff --git a/packages/auth/scripts/dev-env.js b/packages/auth/scripts/dev-env.js new file mode 100644 index 000000000..f9093c8dc --- /dev/null +++ b/packages/auth/scripts/dev-env.js @@ -0,0 +1,58 @@ +// @ts-check +import { networkInterfaces } from "os"; +import path from "path"; + +import { makeGetDevEnv } from "@acme/scripts/dev-env.js"; +import { getUnusedPort, sanititisePort } from "@acme/scripts/http.js"; + +export const getDevEnv = makeGetDevEnv([["AUTH_URL", getAuthUrlFallback]], { + source: `/packages/auth/scripts/dev-env.js`, + readme: path.relative( + process.cwd(), + path.resolve(import.meta.dirname, "../README.md"), + ), +}); + +const NEXTJS_INITIAL_PORT = + // eslint-disable-next-line no-restricted-properties + sanititisePort(process.env.PORT) ?? + // TODO: read port from command? + null ?? + 3000; + +async function getAuthUrlFallback() { + const protocol = "http"; + const port = await getUnusedPort(NEXTJS_INITIAL_PORT); + const interfaces = networkInterfaces(); + /** @type {string[]} */ + const addresses = []; + for (const name in interfaces) { + const candidates = interfaces[name] ?? []; + for (const candidate of candidates) { + if (candidate.family === "IPv4" && !candidate.internal) { + addresses.push(candidate.address); + } + } + } + const [address] = addresses.sort( + (a, b) => getIPv4Priority(a) - getIPv4Priority(b), + ); + return address + ? `${protocol}://${address}:${port}` + : // this will not work in expo app, but trying anyway... + `${protocol}://localhost:${port}`; +} + +/** @param {string} ipv4 */ +function getIPv4Priority(ipv4) { + switch (true) { + case ipv4.startsWith("127."): + return 1000; + case ipv4.startsWith("192.168.1."): + return -100; + case ipv4.startsWith("192.168."): + return -1; + default: + return 0; + } +} diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json index 8bbf4dc29..21c1d97d4 100644 --- a/packages/auth/tsconfig.json +++ b/packages/auth/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, - "include": ["src", "*.ts"], + "include": ["src", "*.ts", "scripts/*.js"], "exclude": ["node_modules"] } diff --git a/packages/scripts/eslint.config.js b/packages/scripts/eslint.config.js new file mode 100644 index 000000000..61cafcb27 --- /dev/null +++ b/packages/scripts/eslint.config.js @@ -0,0 +1,10 @@ +import baseConfig, { restrictEnvAccess } from "@acme/eslint-config/base"; + +/** @type {import('typescript-eslint').Config} */ +export default [ + { + ignores: [], + }, + ...baseConfig, + ...restrictEnvAccess, +]; diff --git a/packages/scripts/package.json b/packages/scripts/package.json new file mode 100644 index 000000000..69c83f2ca --- /dev/null +++ b/packages/scripts/package.json @@ -0,0 +1,33 @@ +{ + "name": "@acme/scripts", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": { + "default": "./src/index.ts" + }, + "./dev-env.js": { + "default": "./src/dev-env.js" + }, + "./http.js": { + "default": "./src/http.js" + } + }, + "license": "MIT", + "scripts": { + "clean": "rm -rf .turbo node_modules", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@acme/eslint-config": "workspace:*", + "@acme/prettier-config": "workspace:*", + "@acme/tsconfig": "workspace:*", + "eslint": "^9.2.0", + "prettier": "^3.2.5", + "typescript": "^5.4.5" + }, + "prettier": "@acme/prettier-config" +} diff --git a/packages/scripts/src/dev-env.js b/packages/scripts/src/dev-env.js new file mode 100644 index 000000000..6d96a4e98 --- /dev/null +++ b/packages/scripts/src/dev-env.js @@ -0,0 +1,100 @@ +// @ts-check +import fs from "fs/promises"; + +/** + * @param {[string, (() => string | Promise)][]} entries + * @param {{ + * log?: boolean, + * source?: string + * readme?: string + * title?: string, + * }} options + */ +export function makeGetDevEnv( + entries, + { + log = true, + source = "/packages/scripts/src/dev-env.js", + readme = "", + title = ` Infering dev env variables from ${source}${readme && ` (see ${readme} for more info)`}:`, + } = {}, +) { + return () => { + /** @type [string, string][] */ + const fallbacks = []; + /** @type {Record} */ + const result = {}; + return entries + .reduce(async (accPromise, [name, fallbackFn]) => { + const acc = await accPromise; + acc[name] = await getEnvVarValue(name, fallbackFn); + // eslint-disable-next-line no-restricted-properties + if (acc[name] !== process.env[name]) { + fallbacks.push([name, acc[name] ?? ""]); + } + return acc; + }, Promise.resolve(result)) + .then( + (result) => ( + log && fallbacks.length + ? (console.log(title), + fallbacks.forEach(([name, value]) => + console.log(` - ${name}=${value}`), + )) + : void 0, + result + ), + ); + }; +} + +/** + * @param {string} name + * @param {(() => string | Promise)} fallbackFn + * @returns {Promise} + */ +export async function getEnvVarValue(name, fallbackFn) { + // eslint-disable-next-line no-restricted-properties + return process.env[name] ?? (await fallbackFn()); +} + +/** + * + * @param {{ + * filePath: string, + * devEnv: Record, + * source?: string, + * title?: string, + * newline?: string + * }} options + */ +export async function addDevEnvToFile({ + filePath, + devEnv, + source = "/packages/scripts/src/dev-env.js", + title = `# AUTOGENERATED BY ${source} -- DO NOT EDIT BELOW THIS LINE`, + newline = "\n", +}) { + let file; + try { + file = await fs.open(filePath, fs.constants.O_RDWR | fs.constants.O_CREAT); + const previousContents = (await file.readFile("utf8")).replace(/\r/g, ""); + const prefix = previousContents.includes(title) + ? previousContents.slice(0, previousContents.indexOf(title)) + : previousContents; + + const contents = [ + prefix.replace(new RegExp(`${newline}{0,}$`), newline.repeat(2)), + title, + newline, + ...Object.entries(devEnv) + .map(([key, value]) => `${key}=${value}`) + .join(newline), + newline, + ].join(""); + const written = await file.write(contents, 0, "utf8"); + await file.truncate(written.bytesWritten); + } finally { + await file?.close(); + } +} diff --git a/packages/scripts/src/http.js b/packages/scripts/src/http.js new file mode 100644 index 000000000..b50b41d87 --- /dev/null +++ b/packages/scripts/src/http.js @@ -0,0 +1,40 @@ +import { createServer } from "http"; + +/** @param {unknown} port */ +export function sanititisePort(port) { + // @ts-expect-error: we are not using the port as a number + const portNumber = Number.parseInt(port, 10); + return Number.isNaN(portNumber) ? null : portNumber; +} + +/** + * @param {number} initialPort + */ +export async function getUnusedPort(initialPort) { + let port = initialPort; + while (await isPortInUse(port)) { + port += 1; + } + return port; +} + +/** + * @param {number} port + */ +export function isPortInUse(port) { + return new Promise((resolve) => { + const server = createServer(); + server.on("error", (err) => { + if ("code" in err && err.code === "EADDRINUSE") { + resolve(true); + } else { + resolve(false); + } + }); + server.on("listening", () => { + server.close(); + resolve(false); + }); + server.listen(port); + }); +} diff --git a/packages/scripts/src/index.js b/packages/scripts/src/index.js new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/packages/scripts/src/index.js @@ -0,0 +1 @@ +export {}; diff --git a/packages/scripts/tsconfig.json b/packages/scripts/tsconfig.json new file mode 100644 index 000000000..87a869cb5 --- /dev/null +++ b/packages/scripts/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@acme/tsconfig/base.json", + "compilerOptions": { + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" + }, + "include": ["src", "*.ts", "*.js"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1f73b717f..63c9c3e85 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,6 +242,9 @@ importers: '@acme/prettier-config': specifier: workspace:* version: link:../../tooling/prettier + '@acme/scripts': + specifier: workspace:* + version: link:../../packages/scripts '@acme/tailwind-config': specifier: workspace:* version: link:../../tooling/tailwind @@ -321,6 +324,9 @@ importers: '@acme/db': specifier: workspace:* version: link:../db + '@acme/scripts': + specifier: workspace:* + version: link:../scripts '@auth/drizzle-adapter': specifier: ^1.1.0 version: 1.1.0 @@ -405,6 +411,27 @@ importers: specifier: ^5.4.5 version: 5.4.5 + packages/scripts: + devDependencies: + '@acme/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@acme/prettier-config': + specifier: workspace:* + version: link:../../tooling/prettier + '@acme/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + eslint: + specifier: ^9.2.0 + version: 9.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + typescript: + specifier: ^5.4.5 + version: 5.4.5 + packages/ui: dependencies: '@hookform/resolvers': @@ -519,7 +546,7 @@ importers: version: 7.34.1(eslint@9.2.0) eslint-plugin-react-hooks: specifier: beta - version: 5.1.0-beta-04b058868c-20240508(eslint@9.2.0) + version: 5.1.0-beta-26f2496093-20240514(eslint@9.2.0) typescript-eslint: specifier: ^7.8.0 version: 7.8.0(eslint@9.2.0)(typescript@5.4.5) @@ -3307,6 +3334,7 @@ packages: are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4385,8 +4413,8 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - eslint-plugin-react-hooks@5.1.0-beta-04b058868c-20240508: - resolution: {integrity: sha512-5SjQe94ffH/jLSXhjE076pGNfM0O5oDVYFvisPfGuC/Lez2E/mpEzKEMESK//yuQGNtSsPgA/7WyQu+qO7bYSA==} + eslint-plugin-react-hooks@5.1.0-beta-26f2496093-20240514: + resolution: {integrity: sha512-nCZD93/KYY5hNAWGhfvvrEXvLFIXJCMu2St7ciHeiWUp/lnS2RVgWawp2kNQamr9Y23C9lUA03TmDRNgbm05vg==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 @@ -4766,6 +4794,7 @@ packages: gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} + deprecated: This package is no longer supported. geist@1.3.0: resolution: {integrity: sha512-IoGBfcqVEYB4bEwsfHd35jF4+X9LHRPYZymHL4YOltHSs9LJa24DYs1Z7rEMQ/lsEvaAIc61Y9aUxgcJaQ8lrg==} @@ -6153,6 +6182,7 @@ packages: npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} @@ -6283,6 +6313,7 @@ packages: osenv@0.1.5: resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} + deprecated: This package is no longer supported. p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} @@ -12499,7 +12530,7 @@ snapshots: object.entries: 1.1.8 object.fromentries: 2.0.8 - eslint-plugin-react-hooks@5.1.0-beta-04b058868c-20240508(eslint@9.2.0): + eslint-plugin-react-hooks@5.1.0-beta-26f2496093-20240514(eslint@9.2.0): dependencies: eslint: 9.2.0 From ecf0fc4a1033784bc1597adeb5869b5fd21df90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Wed, 22 May 2024 13:11:10 +0200 Subject: [PATCH 02/11] refactor: split `write-dev-env` into separate script that reads the env files for itself This is needed to know which env variables will be actually read afterwards, and avoid unnecessary logic/logging --- apps/nextjs/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 8d96b4c6f..1e22151e6 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -11,7 +11,8 @@ "lint": "eslint", "start": "pnpm with-env next start", "typecheck": "tsc --noEmit", - "with-dev-env": "node ./scripts/write-dev-env.js && pnpm with-env", + "write-dev-env": "pnpm with-env node ./scripts/write-dev-env.js", + "with-dev-env": "pnpm write-dev-env && pnpm with-env", "with-env": "dotenv -e ../../.env --" }, "dependencies": { From caeba197befbf5f13c747326611f4eb32797548b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 11:14:43 +0200 Subject: [PATCH 03/11] refactor: allow to return an array with a value and a list of nested fallback functions for dependencies --- packages/scripts/src/dev-env.js | 68 ++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/packages/scripts/src/dev-env.js b/packages/scripts/src/dev-env.js index 6d96a4e98..52053a264 100644 --- a/packages/scripts/src/dev-env.js +++ b/packages/scripts/src/dev-env.js @@ -2,7 +2,11 @@ import fs from "fs/promises"; /** - * @param {[string, (() => string | Promise)][]} entries + * @typedef {() => string | Promise | [string, ...[string, FallbackFn][]]} FallbackFn + */ + +/** + * @param {[string, FallbackFn][]} entries * @param {{ * log?: boolean, * source?: string @@ -22,40 +26,44 @@ export function makeGetDevEnv( return () => { /** @type [string, string][] */ const fallbacks = []; - /** @type {Record} */ - const result = {}; - return entries - .reduce(async (accPromise, [name, fallbackFn]) => { - const acc = await accPromise; - acc[name] = await getEnvVarValue(name, fallbackFn); - // eslint-disable-next-line no-restricted-properties - if (acc[name] !== process.env[name]) { - fallbacks.push([name, acc[name] ?? ""]); - } - return acc; - }, Promise.resolve(result)) - .then( - (result) => ( - log && fallbacks.length - ? (console.log(title), - fallbacks.forEach(([name, value]) => - console.log(` - ${name}=${value}`), - )) - : void 0, - result - ), - ); + return reduceEnvVarValues(entries, undefined, fallbacks).then( + (result) => ( + log && fallbacks.length + ? (console.log(title), + fallbacks.forEach(([name, value]) => + console.log(` - ${name}=${value}`), + )) + : void 0, + result + ), + ); }; } /** - * @param {string} name - * @param {(() => string | Promise)} fallbackFn - * @returns {Promise} + * @param {[string, FallbackFn][]} entries + * @param {Record | undefined} result + * @param {[string, string][]} fallbacks + * @returns {Promise>} */ -export async function getEnvVarValue(name, fallbackFn) { - // eslint-disable-next-line no-restricted-properties - return process.env[name] ?? (await fallbackFn()); +export async function reduceEnvVarValues(entries, result = {}, fallbacks = []) { + return entries.reduce(async (accPromise, [name, fallbackFn]) => { + const acc = await accPromise; + // eslint-disable-next-line no-restricted-properties + const envValue = process.env[name]; + const fallbackOrEntries = envValue ?? (await fallbackFn()); + const [fallbackValue, ...fallbackEntries] = Array.isArray(fallbackOrEntries) + ? fallbackOrEntries + : [fallbackOrEntries]; + if (fallbackEntries.length) { + await reduceEnvVarValues(fallbackEntries, acc, fallbacks); + } + acc[name] = fallbackValue; + if (acc[name] !== envValue) { + fallbacks.push([name, acc[name] ?? ""]); + } + return acc; + }, Promise.resolve(result)); } /** From 4282647610682058481463f5433c925d9c1b9b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 11:23:43 +0200 Subject: [PATCH 04/11] refactor: split env var inference so they can be reused --- packages/auth/scripts/dev-env.js | 58 +++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/auth/scripts/dev-env.js b/packages/auth/scripts/dev-env.js index f9093c8dc..615a5837a 100644 --- a/packages/auth/scripts/dev-env.js +++ b/packages/auth/scripts/dev-env.js @@ -5,24 +5,41 @@ import path from "path"; import { makeGetDevEnv } from "@acme/scripts/dev-env.js"; import { getUnusedPort, sanititisePort } from "@acme/scripts/http.js"; -export const getDevEnv = makeGetDevEnv([["AUTH_URL", getAuthUrlFallback]], { - source: `/packages/auth/scripts/dev-env.js`, - readme: path.relative( - process.cwd(), - path.resolve(import.meta.dirname, "../README.md"), - ), +export const EnvVars = Object.freeze({ + HOSTNAME: "HOSTNAME", + PORT: "PORT", + AUTH_URL: "AUTH_URL", }); -const NEXTJS_INITIAL_PORT = +export const getDevEnv = makeGetDevEnv( + [ + [EnvVars.HOSTNAME, hostnameFallbackFn], + [EnvVars.PORT, portFallbackFn], + [EnvVars.AUTH_URL, authUrlFallbackFn], + ], + { + source: `/packages/auth/scripts/dev-env.js`, + readme: path.relative( + process.cwd(), + path.resolve(import.meta.dirname, "../README.md"), + ), + }, +); + +export const NEXTJS_INITIAL_PORT = // eslint-disable-next-line no-restricted-properties sanititisePort(process.env.PORT) ?? // TODO: read port from command? null ?? 3000; -async function getAuthUrlFallback() { - const protocol = "http"; - const port = await getUnusedPort(NEXTJS_INITIAL_PORT); +// this is not going to work in Expo, but using it anyway as default +const NEXTJS_INITIAL_HOSTNAME = "localhost"; + +const NEXTJS_INITIAL_PROTOCOL = "http"; + +/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ +export function hostnameFallbackFn() { const interfaces = networkInterfaces(); /** @type {string[]} */ const addresses = []; @@ -34,17 +51,26 @@ async function getAuthUrlFallback() { } } } - const [address] = addresses.sort( + const [address = NEXTJS_INITIAL_HOSTNAME] = addresses.sort( (a, b) => getIPv4Priority(a) - getIPv4Priority(b), ); - return address - ? `${protocol}://${address}:${port}` - : // this will not work in expo app, but trying anyway... - `${protocol}://localhost:${port}`; + return address; +} + +/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ +export async function portFallbackFn() { + return getUnusedPort(NEXTJS_INITIAL_PORT).then(String); +} + +/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ +export function authUrlFallbackFn() { + const protocol = NEXTJS_INITIAL_PROTOCOL; + + return `${protocol}://$${EnvVars.HOSTNAME}:$${EnvVars.PORT}`; } /** @param {string} ipv4 */ -function getIPv4Priority(ipv4) { +export function getIPv4Priority(ipv4) { switch (true) { case ipv4.startsWith("127."): return 1000; From 130e8d689f0ed9a3ff17df80361df0806d6e64c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 11:24:16 +0200 Subject: [PATCH 05/11] refactor: use `.env.development.local`, read it to provide `--hostname` parameter --- apps/nextjs/package.json | 5 +++-- apps/nextjs/scripts/write-dev-env.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 1e22151e6..d6e0b0c86 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -6,13 +6,14 @@ "scripts": { "build": "pnpm with-env next build", "clean": "git clean -xdf .next .turbo node_modules", - "dev": "pnpm with-dev-env next dev", + "next:dev": "next dev --hostname $HOSTNAME", + "dev": "pnpm with-dev-env pnpm next:dev", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", "start": "pnpm with-env next start", "typecheck": "tsc --noEmit", "write-dev-env": "pnpm with-env node ./scripts/write-dev-env.js", - "with-dev-env": "pnpm write-dev-env && pnpm with-env", + "with-dev-env": "pnpm write-dev-env && dotenv -e ../../.env -e ./.env.development.local --", "with-env": "dotenv -e ../../.env --" }, "dependencies": { diff --git a/apps/nextjs/scripts/write-dev-env.js b/apps/nextjs/scripts/write-dev-env.js index 06e06b237..eed1156fd 100644 --- a/apps/nextjs/scripts/write-dev-env.js +++ b/apps/nextjs/scripts/write-dev-env.js @@ -2,7 +2,7 @@ import * as auth from "@acme/auth/scripts/dev-env.js"; import { addDevEnvToFile } from "@acme/scripts/dev-env.js"; -const filePath = ".env.local"; +const filePath = ".env.development.local"; const devEnv = { ...(await auth.getDevEnv()), }; From 6c4df51cb218c97fb099ba8ef71791f488280119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 11:44:35 +0200 Subject: [PATCH 06/11] refactor: add generics --- packages/auth/scripts/dev-env.js | 11 +++++++---- packages/scripts/src/dev-env.js | 23 ++++++++++++++--------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/auth/scripts/dev-env.js b/packages/auth/scripts/dev-env.js index 615a5837a..cde8e5ca4 100644 --- a/packages/auth/scripts/dev-env.js +++ b/packages/auth/scripts/dev-env.js @@ -11,6 +11,9 @@ export const EnvVars = Object.freeze({ AUTH_URL: "AUTH_URL", }); +/** @typedef {keyof typeof EnvVars} EnvVarsKeys */ +/** @typedef {import('@acme/scripts/dev-env.js').FallbackFn} AuthFallbackFn */ + export const getDevEnv = makeGetDevEnv( [ [EnvVars.HOSTNAME, hostnameFallbackFn], @@ -38,7 +41,7 @@ const NEXTJS_INITIAL_HOSTNAME = "localhost"; const NEXTJS_INITIAL_PROTOCOL = "http"; -/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ +/** @type {AuthFallbackFn} */ export function hostnameFallbackFn() { const interfaces = networkInterfaces(); /** @type {string[]} */ @@ -57,12 +60,12 @@ export function hostnameFallbackFn() { return address; } -/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ -export async function portFallbackFn() { +/** @type {AuthFallbackFn} */ +export function portFallbackFn() { return getUnusedPort(NEXTJS_INITIAL_PORT).then(String); } -/** @type {import('@acme/scripts/dev-env.js').FallbackFn} */ +/** @type {AuthFallbackFn} */ export function authUrlFallbackFn() { const protocol = NEXTJS_INITIAL_PROTOCOL; diff --git a/packages/scripts/src/dev-env.js b/packages/scripts/src/dev-env.js index 52053a264..9c2d7cfe9 100644 --- a/packages/scripts/src/dev-env.js +++ b/packages/scripts/src/dev-env.js @@ -2,11 +2,13 @@ import fs from "fs/promises"; /** - * @typedef {() => string | Promise | [string, ...[string, FallbackFn][]]} FallbackFn + * @template {string} Key + * @typedef {() => string | Promise | [string, ...[Key, FallbackFn][]]} FallbackFn */ /** - * @param {[string, FallbackFn][]} entries + * @template {string} Key + * @param {[Key, FallbackFn][]} entries * @param {{ * log?: boolean, * source?: string @@ -23,8 +25,8 @@ export function makeGetDevEnv( title = ` Infering dev env variables from ${source}${readme && ` (see ${readme} for more info)`}:`, } = {}, ) { - return () => { - /** @type [string, string][] */ + return async () => { + /** @type [Key, string][] */ const fallbacks = []; return reduceEnvVarValues(entries, undefined, fallbacks).then( (result) => ( @@ -41,12 +43,14 @@ export function makeGetDevEnv( } /** - * @param {[string, FallbackFn][]} entries - * @param {Record | undefined} result - * @param {[string, string][]} fallbacks - * @returns {Promise>} + * @template {string} Key + * @param {[Key, FallbackFn][]} entries + * @param {Partial> | undefined} result + * @param {[Key, string][]} fallbacks + * @returns {Promise>>} */ -export async function reduceEnvVarValues(entries, result = {}, fallbacks = []) { +export function reduceEnvVarValues(entries, result = {}, fallbacks = []) { + // @ts-expect-error: `reduce` call infering it's returning a tuple instead of the partial record? return entries.reduce(async (accPromise, [name, fallbackFn]) => { const acc = await accPromise; // eslint-disable-next-line no-restricted-properties @@ -58,6 +62,7 @@ export async function reduceEnvVarValues(entries, result = {}, fallbacks = []) { if (fallbackEntries.length) { await reduceEnvVarValues(fallbackEntries, acc, fallbacks); } + // @ts-expect-error: not accepting the `string` value? acc[name] = fallbackValue; if (acc[name] !== envValue) { fallbacks.push([name, acc[name] ?? ""]); From 88dc1bcc176662518a6bf5378ed6df1e34d13484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 13:14:43 +0200 Subject: [PATCH 07/11] refactor: write to root folder so other apps cas use same env vars --- apps/nextjs/package.json | 2 +- apps/nextjs/scripts/write-dev-env.js | 4 ++-- package.json | 3 ++- turbo.json | 4 ++++ 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index d6e0b0c86..309e176c7 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -13,7 +13,7 @@ "start": "pnpm with-env next start", "typecheck": "tsc --noEmit", "write-dev-env": "pnpm with-env node ./scripts/write-dev-env.js", - "with-dev-env": "pnpm write-dev-env && dotenv -e ../../.env -e ./.env.development.local --", + "with-dev-env": "dotenv -e ../../.env -e ../../.env.local --", "with-env": "dotenv -e ../../.env --" }, "dependencies": { diff --git a/apps/nextjs/scripts/write-dev-env.js b/apps/nextjs/scripts/write-dev-env.js index eed1156fd..5e33722dd 100644 --- a/apps/nextjs/scripts/write-dev-env.js +++ b/apps/nextjs/scripts/write-dev-env.js @@ -2,12 +2,12 @@ import * as auth from "@acme/auth/scripts/dev-env.js"; import { addDevEnvToFile } from "@acme/scripts/dev-env.js"; -const filePath = ".env.development.local"; +const filePath = "../../.env.local"; const devEnv = { ...(await auth.getDevEnv()), }; -addDevEnvToFile({ +await addDevEnvToFile({ filePath, devEnv, source: "/apps/nextjs/scripts/write-dev-env.js", diff --git a/package.json b/package.json index b7da174ba..8dfc5cb05 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "lint:ws": "pnpm dlx sherif@latest", "postinstall": "pnpm lint:ws", "typecheck": "turbo typecheck", - "ui-add": "pnpm -F ui ui-add" + "ui-add": "pnpm -F ui ui-add", + "write-dev-env": "pnpm turbo run write-dev-env" }, "devDependencies": { "@acme/prettier-config": "workspace:*", diff --git a/turbo.json b/turbo.json index 6f2cbbb94..c807992d1 100644 --- a/turbo.json +++ b/turbo.json @@ -18,9 +18,13 @@ ] }, "dev": { + "dependsOn": ["@acme/nextjs#write-dev-env"], "persistent": true, "cache": false }, + "write-dev-env": { + "cache": false + }, "format": { "outputs": ["node_modules/.cache/.prettiercache"], "outputMode": "new-only" From 6e4a0d3e92a8432d20a8a99a6be1c9e1e080bd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 13:15:07 +0200 Subject: [PATCH 08/11] refactor: expose en var for Expo API base url --- apps/expo/package.json | 10 +++--- apps/expo/src/utils/base-url.tsx | 56 +++++++++++++++++++++----------- packages/auth/scripts/dev-env.js | 6 ++-- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/apps/expo/package.json b/apps/expo/package.json index 09d332eea..bebfed568 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -5,14 +5,16 @@ "main": "expo-router/entry", "scripts": { "clean": "git clean -xdf .expo .turbo node_modules", - "dev": "expo start", - "dev:android": "expo start --android", - "dev:ios": "expo start --ios", + "dev": "pnpm with-dev-env expo start", + "dev:android": "pnpm with-dev-env expo start --android", + "dev:ios": "pnpm with-dev-env expo start --ios", "android": "expo run:android", "ios": "expo run:ios", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "with-dev-env": "dotenv -e ../../.env -e ../../.env.local --", + "with-env": "dotenv -e ../../.env --" }, "dependencies": { "@bacons/text-decoder": "^0.0.0", diff --git a/apps/expo/src/utils/base-url.tsx b/apps/expo/src/utils/base-url.tsx index 94dfbbc3f..4f99d9c57 100644 --- a/apps/expo/src/utils/base-url.tsx +++ b/apps/expo/src/utils/base-url.tsx @@ -1,26 +1,44 @@ import Constants from "expo-constants"; +const NAME = "EXPO_PUBLIC_API_BASE_URL"; + /** * Extend this function when going to production by * setting the baseUrl to your production API URL. */ -export const getBaseUrl = () => { - /** - * Gets the IP address of your host-machine. If it cannot automatically find it, - * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm - * you don't have anything else running on it, or you'd have to change it. - * - * **NOTE**: This is only for development. In production, you'll want to set the - * baseUrl to your production API URL. - */ - const debuggerHost = Constants.expoConfig?.hostUri; - const localhost = debuggerHost?.split(":")[0]; +export const getBaseUrl = + process.env.NODE_ENV === "production" + ? () => { + if (!process.env[NAME]) { + throw new Error( + `Failed to get API base url from \`${NAME}\` env var, which is required to be set manually in production.`, + ); + } + return process.env[NAME]; + } + : () => { + /** + * If the environment variable is set, use it. + */ + if (process.env[NAME]) { + return process.env[NAME]; + } + + /** + * Gets the IP address of your host-machine. If it cannot automatically find it, + * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm + * you don't have anything else running on it, or you'd have to change it. + * + * **NOTE**: This is only for development. In production, you'll want to set the + * baseUrl to your production API URL. + */ + const debuggerHost = Constants.expoConfig?.hostUri; + const localhost = debuggerHost?.split(":")[0]; - if (!localhost) { - // return "https://turbo.t3.gg"; - throw new Error( - "Failed to get localhost. Please point to your production server.", - ); - } - return `http://${localhost}:3000`; -}; + if (!localhost) { + throw new Error( + `Failed to get local API base url. Please point to your local server using \`${NAME}\` env var.`, + ); + } + return `http://${localhost}:3000`; + }; diff --git a/packages/auth/scripts/dev-env.js b/packages/auth/scripts/dev-env.js index cde8e5ca4..4902e079d 100644 --- a/packages/auth/scripts/dev-env.js +++ b/packages/auth/scripts/dev-env.js @@ -9,6 +9,7 @@ export const EnvVars = Object.freeze({ HOSTNAME: "HOSTNAME", PORT: "PORT", AUTH_URL: "AUTH_URL", + EXPO_PUBLIC_API_BASE_URL: "EXPO_PUBLIC_API_BASE_URL", }); /** @typedef {keyof typeof EnvVars} EnvVarsKeys */ @@ -18,7 +19,8 @@ export const getDevEnv = makeGetDevEnv( [ [EnvVars.HOSTNAME, hostnameFallbackFn], [EnvVars.PORT, portFallbackFn], - [EnvVars.AUTH_URL, authUrlFallbackFn], + [EnvVars.AUTH_URL, baseUrlFallbackFn], + [EnvVars.EXPO_PUBLIC_API_BASE_URL, baseUrlFallbackFn], ], { source: `/packages/auth/scripts/dev-env.js`, @@ -66,7 +68,7 @@ export function portFallbackFn() { } /** @type {AuthFallbackFn} */ -export function authUrlFallbackFn() { +export function baseUrlFallbackFn() { const protocol = NEXTJS_INITIAL_PROTOCOL; return `${protocol}://$${EnvVars.HOSTNAME}:$${EnvVars.PORT}`; From d28989f3aaa98e95a226b29f738101fe0b52bcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 13:42:55 +0200 Subject: [PATCH 09/11] refactor: split env vars between packages, add helper to make dev env objects (by populating its results onto the following one) --- apps/nextjs/scripts/write-dev-env.js | 7 +++--- packages/api/README.md | 9 ++++++++ packages/api/package.json | 4 +++- packages/api/scripts/dev-env.js | 34 ++++++++++++++++++++++++++++ packages/api/tsconfig.json | 2 +- packages/auth/package.json | 4 +--- packages/auth/scripts/dev-env.js | 4 ++-- packages/scripts/src/dev-env.js | 17 ++++++++++++++ pnpm-lock.yaml | 3 +++ 9 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 packages/api/README.md create mode 100644 packages/api/scripts/dev-env.js diff --git a/apps/nextjs/scripts/write-dev-env.js b/apps/nextjs/scripts/write-dev-env.js index 5e33722dd..60294f2d3 100644 --- a/apps/nextjs/scripts/write-dev-env.js +++ b/apps/nextjs/scripts/write-dev-env.js @@ -1,11 +1,10 @@ // @ts-check +import * as api from "@acme/api/scripts/dev-env.js"; import * as auth from "@acme/auth/scripts/dev-env.js"; -import { addDevEnvToFile } from "@acme/scripts/dev-env.js"; +import { addDevEnvToFile, makeDevEnv } from "@acme/scripts/dev-env.js"; const filePath = "../../.env.local"; -const devEnv = { - ...(await auth.getDevEnv()), -}; +const devEnv = await makeDevEnv([api.getDevEnv, auth.getDevEnv]); await addDevEnvToFile({ filePath, diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 000000000..c5ef9b3ac --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,9 @@ +# packages/api + +## FAQ + +### What's going on with `EXPO_PUBLIC_API_BASE_URL`? + +The `EXPO_PUBLIC_API_BASE_URL` is needed to resolve Next.js' exposed API from Expo. This variable needs to be specified when building the app in production, and it should point the production deployment of the Next.js. + +The `pnpm dev` command will try to infer the URL automatically, but it may not always work and you may get a different IP address and a different port. If that happens, you can always set it manually in your `.env` file or by setting it before the command, such as `EXPO_PUBLIC_API_BASE_URL=http://x.x.x.x:x pnpm dev`. diff --git a/packages/api/package.json b/packages/api/package.json index 656ca98d9..e10256cb3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -7,7 +7,8 @@ ".": { "types": "./dist/index.d.ts", "default": "./src/index.ts" - } + }, + "./scripts/dev-env.js": "./scripts/dev-env.js" }, "license": "MIT", "scripts": { @@ -21,6 +22,7 @@ "dependencies": { "@acme/auth": "workspace:*", "@acme/db": "workspace:*", + "@acme/scripts": "workspace:*", "@acme/validators": "workspace:*", "@trpc/server": "11.0.0-rc.364", "superjson": "2.2.1", diff --git a/packages/api/scripts/dev-env.js b/packages/api/scripts/dev-env.js new file mode 100644 index 000000000..4c16f65b4 --- /dev/null +++ b/packages/api/scripts/dev-env.js @@ -0,0 +1,34 @@ +// @ts-check +import path from "path"; + +import { + EnvVars as AuthEnvVars, + baseUrlFallbackFn, + hostnameFallbackFn, + portFallbackFn, +} from "@acme/auth/scripts/dev-env.js"; +import { makeGetDevEnv } from "@acme/scripts/dev-env.js"; + +export const EnvVars = Object.freeze({ + HOSTNAME: AuthEnvVars.HOSTNAME, + PORT: AuthEnvVars.PORT, + EXPO_PUBLIC_API_BASE_URL: "EXPO_PUBLIC_API_BASE_URL", +}); + +/** @typedef {keyof typeof EnvVars} EnvVarsKeys */ +/** @typedef {import('@acme/scripts/dev-env.js').FallbackFn} AuthFallbackFn */ + +export const getDevEnv = makeGetDevEnv( + [ + [EnvVars.HOSTNAME, hostnameFallbackFn], + [EnvVars.PORT, portFallbackFn], + [EnvVars.EXPO_PUBLIC_API_BASE_URL, baseUrlFallbackFn], + ], + { + source: `/packages/api/scripts/dev-env.js`, + readme: path.relative( + process.cwd(), + path.resolve(import.meta.dirname, "../README.md"), + ), + }, +); diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index ed40c5d96..f754bb3e8 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -4,6 +4,6 @@ "outDir": "dist", "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" }, - "include": ["src"], + "include": ["src", "scripts/*.js"], "exclude": ["node_modules"] } diff --git a/packages/auth/package.json b/packages/auth/package.json index 40e97b148..b420d7904 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -9,9 +9,7 @@ "default": "./src/index.ts" }, "./env": "./env.ts", - "./scripts/dev-env.js": { - "default": "./scripts/dev-env.js" - } + "./scripts/dev-env.js": "./scripts/dev-env.js" }, "license": "MIT", "scripts": { diff --git a/packages/auth/scripts/dev-env.js b/packages/auth/scripts/dev-env.js index 4902e079d..ae55d0d2a 100644 --- a/packages/auth/scripts/dev-env.js +++ b/packages/auth/scripts/dev-env.js @@ -39,9 +39,9 @@ export const NEXTJS_INITIAL_PORT = 3000; // this is not going to work in Expo, but using it anyway as default -const NEXTJS_INITIAL_HOSTNAME = "localhost"; +export const NEXTJS_INITIAL_HOSTNAME = "localhost"; -const NEXTJS_INITIAL_PROTOCOL = "http"; +export const NEXTJS_INITIAL_PROTOCOL = "http"; /** @type {AuthFallbackFn} */ export function hostnameFallbackFn() { diff --git a/packages/scripts/src/dev-env.js b/packages/scripts/src/dev-env.js index 9c2d7cfe9..8eadcc8cc 100644 --- a/packages/scripts/src/dev-env.js +++ b/packages/scripts/src/dev-env.js @@ -1,5 +1,6 @@ // @ts-check import fs from "fs/promises"; +import dotenv from "dotenv"; /** * @template {string} Key @@ -71,6 +72,22 @@ export function reduceEnvVarValues(entries, result = {}, fallbacks = []) { }, Promise.resolve(result)); } +/** + * @template {string} Key + * @param {ReturnType>[]} getDevEnvs + * @param {Partial>} result + */ +export async function makeDevEnv(getDevEnvs, result = {}) { + return getDevEnvs.reduce(async (accPromise, getDevEnv) => { + const acc = await accPromise; + const devEnv = await getDevEnv(); + // @ts-expect-error: typed as `string` instead of `string | undefined` + // eslint-disable-next-line no-restricted-properties + dotenv.populate(process.env, devEnv); + return { ...acc, ...devEnv }; + }, Promise.resolve(result)); +} + /** * * @param {{ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63c9c3e85..19c8f01d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,6 +287,9 @@ importers: '@acme/db': specifier: workspace:* version: link:../db + '@acme/scripts': + specifier: workspace:* + version: link:../scripts '@acme/validators': specifier: workspace:* version: link:../validators From beae1339f61144af554f0e24ec1636095947acfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 15:03:46 +0200 Subject: [PATCH 10/11] fix: change predicate to find auth session cookie --- apps/nextjs/src/app/api/auth/[...nextauth]/route.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts index 9ae30f283..e1f297cad 100644 --- a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts +++ b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts @@ -32,11 +32,12 @@ export const GET = async ( cookies().delete(EXPO_COOKIE_NAME); const authResponse = await DEFAULT_GET(req); + const setCookie = authResponse.headers .getSetCookie() - .find((cookie) => cookie.startsWith("authjs.session-token")); + .find((cookie) => AUTH_COOKIE_PATTERN.test(cookie)); const match = setCookie?.match(AUTH_COOKIE_PATTERN)?.[1]; - + if (!match) throw new Error( "Unable to find session cookie: " + From 39b7cecadff8755b357394bcae055a7005dd1d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oscar=20Chic=20Farr=C3=A9?= Date: Tue, 28 May 2024 15:56:39 +0200 Subject: [PATCH 11/11] refactor: define static prefix and consider other generated sections to avoid removing them --- packages/scripts/src/dev-env.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/scripts/src/dev-env.js b/packages/scripts/src/dev-env.js index 8eadcc8cc..30f31f1a8 100644 --- a/packages/scripts/src/dev-env.js +++ b/packages/scripts/src/dev-env.js @@ -88,6 +88,8 @@ export async function makeDevEnv(getDevEnvs, result = {}) { }, Promise.resolve(result)); } +const DEV_ENV_PREFIX = "[dev-env]"; + /** * * @param {{ @@ -102,25 +104,34 @@ export async function addDevEnvToFile({ filePath, devEnv, source = "/packages/scripts/src/dev-env.js", - title = `# AUTOGENERATED BY ${source} -- DO NOT EDIT BELOW THIS LINE`, + title = `AUTOGENERATED BY ${source} -- DO NOT EDIT THIS LINE OR BELOW`, newline = "\n", }) { let file; try { file = await fs.open(filePath, fs.constants.O_RDWR | fs.constants.O_CREAT); + + const separator = `# ${DEV_ENV_PREFIX} ${title}`; const previousContents = (await file.readFile("utf8")).replace(/\r/g, ""); - const prefix = previousContents.includes(title) - ? previousContents.slice(0, previousContents.indexOf(title)) - : previousContents; + const [before = "", after = ""] = previousContents.includes(separator) + ? previousContents.split(separator) + : [previousContents]; + + const prefix = before.replace( + new RegExp(`${newline}{0,}$`), + newline.repeat(2), + ); + const suffix = after.slice(after.indexOf(`# ${DEV_ENV_PREFIX}`)); const contents = [ - prefix.replace(new RegExp(`${newline}{0,}$`), newline.repeat(2)), - title, + prefix, + separator, newline, - ...Object.entries(devEnv) + ...(Object.entries(devEnv) .map(([key, value]) => `${key}=${value}`) - .join(newline), + .join(newline) || "## No dev env variables infered"), newline, + ...(suffix ? [newline, suffix] : []), ].join(""); const written = await file.write(contents, 0, "utf8"); await file.truncate(written.bytesWritten);