refactor: remove old mobile project

This commit is contained in:
2026-08-25 09:30:23 +02:00
parent 5201c64722
commit fc2014d50f
42 changed files with 331 additions and 299 deletions
+17 -17
View File
@@ -1,20 +1,20 @@
BASANGO_API_HOST=localhost
# Committed development defaults. Override them in an ignored .env.dev,
# .env.test, .env.prod, or .env.local file.
NODE_ENV=dev
# API
BASANGO_API_HOST=127.0.0.1
BASANGO_API_PORT=3080
BASANGO_API_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
BASANGO_API_KEY=some_secret_key
BASANGO_API_CRAWLER_TOKEN=some_secret_key
BASANGO_API_JWT_SECRET=some_secret_key
BASANGO_ADMIN_EMAIL="ngandubernard@gmail.com"
BASANGO_ADMIN_NAME="Bernard Ngandu"
BASANGO_ADMIN_PASSWORD=some_secret_key
BETTER_AUTH_SECRET=some_secret_key
BETTER_AUTH_COOKIE_DOMAIN=".basango.ngandu.dev"
BETTER_AUTH_URL="https://localhost:3080"
BASANGO_API_CRAWLER_TOKEN=development-crawler-token
BASANGO_DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/app
BASANGO_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
BASANGO_LOGGER_LEVEL=debug
BASANGO_ENCRYPTION_KEY=some_secret_key
BASANGO_LOGGER_PRETTY=true
BASANGO_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/app?serverVersion=16&charset=utf8"
# Authentication
BETTER_AUTH_SECRET=development-only-better-auth-secret
BETTER_AUTH_URL=http://localhost:3080
# Dashboard (only VITE_ variables are exposed to browser code)
VITE_PUBLIC_API_URL=http://localhost:3080
VITE_PUBLIC_URL=http://localhost:3000
+1 -3
View File
@@ -15,9 +15,7 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.env.local
.env.*.local
.env.*.local.*
.env.*
.vercel
*.tsbuildinfo
.turbo
-8
View File
@@ -1,8 +0,0 @@
{
"recommendations": [
"expo.vscode-expo-tools",
"bradlc.vscode-tailwindcss",
"expo.vscode-expo-tools",
"yoavbls.pretty-ts-errors"
]
}
-28
View File
@@ -1,28 +0,0 @@
{
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"files.autoSave": "onFocusChange",
"search.exclude": {
"**/node_modules": true
},
"terminal.integrated.localEchoStyle": "dim",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.autoImportFileExcludePatterns": [
"next/router.d.ts",
"next/dist/client/router.d.ts"
],
"typescript.tsdk": "node_modules/typescript/lib"
}
+5 -5
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -30,6 +30,8 @@
| Scope | Link |
|-------------------|-----------------------------------------------|
| Architecture | [ARCHITECTURE.md](./docs/architecture.md) |
| Environment | [ENVIRONMENT.md](./docs/environment.md) |
| Deployment | [DEPLOYMENT.md](./docs/deployment.md) |
| TypeScript apps | [README.md](./docs/web/README.md) |
| TypeScript style | [CODE-STYLE.md](./docs/web/code-style.md) |
| Contributing | [CONTRIBUTING.md](./CONTRIBUTING.md) |
+2 -2
View File
@@ -28,8 +28,8 @@
"private": true,
"scripts": {
"auth:create-admin": "bun run src/scripts/create-admin.ts",
"dev": "bun run --hot src/index.ts",
"start": "NODE_ENV=production bun run src/index.ts",
"dev": "NODE_ENV=dev bun run --hot src/index.ts",
"start": "NODE_ENV=prod bun run src/index.ts",
"typecheck": "tsc --noEmit"
},
"type": "module"
+1 -1
View File
@@ -9,7 +9,7 @@ import { v7 as uuidv7 } from "uuid";
import { sendPasswordResetEmail } from "#api/utils/password-reset-email";
const isProduction = env.NODE_ENV === "production";
const isProduction = env.NODE_ENV === "prod";
const baseURL = env.BETTER_AUTH_URL?.trim() ?? `http://localhost:${config.api.server.port}`;
const cookieDomain = env.BETTER_AUTH_COOKIE_DOMAIN?.trim();
const secret =
+1 -1
View File
@@ -25,7 +25,7 @@ export async function sendPasswordResetEmail(input: PasswordResetEmail): Promise
const apiKey = env.BASANGO_RESEND_API_KEY?.trim();
if (!apiKey) {
if (env.NODE_ENV === "production") {
if (env.NODE_ENV === "prod") {
throw new Error("BASANGO_RESEND_API_KEY is required to send password reset emails.");
}
-3
View File
@@ -1,3 +0,0 @@
VITE_PUBLIC_API_URL=https://api.basango.ngandu.dev
VITE_PUBLIC_URL=https://dashboard.basango.ngandu.dev
+1 -2
View File
@@ -33,9 +33,8 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
# env files are defined at the repository root
.env*
!.env.production
# vercel
.vercel
+3 -3
View File
@@ -49,10 +49,10 @@
"name": "@basango/dashboard",
"private": true,
"scripts": {
"build": "vite build",
"build": "vite build --mode prod",
"clean": "rm -rf .output .tanstack node_modules",
"dev": "vite dev",
"start": "NODE_ENV=production node .output/server/index.mjs",
"dev": "vite dev --mode dev",
"start": "NODE_ENV=prod node .output/server/index.mjs",
"typecheck": "tsc --noEmit"
},
"type": "module"
+33 -15
View File
@@ -1,22 +1,40 @@
import { normalizeNodeEnvironment, readEnvFiles } from "@basango/domain/config/environment";
import tailwindcss from "@tailwindcss/vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { nitro } from "nitro/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
tanstackStart({
router: {
generatedRouteTree: "./routeTree.gen.ts",
routesDirectory: "./routes",
},
}),
nitro(),
tailwindcss(),
viteReact(),
],
resolve: {
tsconfigPaths: true,
},
export default defineConfig(({ mode }) => {
const nodeEnvironment = normalizeNodeEnvironment(mode);
applyPublicEnvironment(nodeEnvironment);
return {
define: {
"process.env.NODE_ENV": JSON.stringify(nodeEnvironment),
},
envDir: false,
plugins: [
tanstackStart({
router: {
generatedRouteTree: "./routeTree.gen.ts",
routesDirectory: "./routes",
},
}),
nitro(),
tailwindcss(),
viteReact(),
],
resolve: {
tsconfigPaths: true,
},
};
});
function applyPublicEnvironment(mode: string): void {
for (const [key, value] of Object.entries(readEnvFiles({ nodeEnvironment: mode }))) {
if (key.startsWith("VITE_") && value !== undefined && process.env[key] === undefined) {
process.env[key] = value;
}
}
}
-43
View File
@@ -1,43 +0,0 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
app-example
# generated native folders
/ios
/android
-1
View File
@@ -1 +0,0 @@
# Basango Mobile
-48
View File
@@ -1,48 +0,0 @@
{
"expo": {
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"backgroundImage": "./assets/images/android-icon-background.png",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false
},
"experiments": {
"reactCompiler": true,
"typedRoutes": true
},
"icon": "./assets/images/icon.png",
"ios": {
"supportsTablet": true
},
"name": "basango",
"newArchEnabled": true,
"orientation": "portrait",
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
},
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain"
}
]
],
"scheme": "basango",
"slug": "basango",
"userInterfaceStyle": "automatic",
"version": "1.0.0",
"web": {
"favicon": "./assets/images/favicon.png",
"output": "static"
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-46
View File
@@ -1,46 +0,0 @@
{
"dependencies": {
"@expo/vector-icons": "^15.0.3",
"@react-navigation/bottom-tabs": "^7.4.0",
"@react-navigation/elements": "^2.6.3",
"@react-navigation/native": "^7.1.8",
"expo": "~54.0.22",
"expo-constants": "~18.0.10",
"expo-font": "~14.0.9",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.10",
"expo-linking": "~8.0.8",
"expo-router": "~6.0.14",
"expo-splash-screen": "~31.0.10",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.8",
"expo-web-browser": "~15.0.9",
"react": "catalog:",
"react-dom": "catalog:",
"react-native": "0.81.5",
"react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~4.1.1",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.5.1"
},
"devDependencies": {
"@types/react": "catalog:",
"typescript": "catalog:"
},
"main": "expo-router/entry",
"name": "@basango/mobile",
"private": true,
"scripts": {
"android": "expo start --android",
"clean": "rm -rf .expo node_modules",
"ios": "expo start --ios",
"lint": "expo lint",
"start": "expo start",
"typecheck": "tsc --noEmit",
"web": "expo start --web"
},
"version": "1.0.0"
}
-5
View File
@@ -1,5 +0,0 @@
import { Stack } from "expo-router";
export default function RootLayout() {
return <Stack />;
}
-15
View File
@@ -1,15 +0,0 @@
import { Text, View } from "react-native";
export default function Index() {
return (
<View
style={{
alignItems: "center",
flex: 1,
justifyContent: "center",
}}
>
<Text>Edit app/index.tsx to edit this screen.</Text>
</View>
);
}
-10
View File
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"paths": {
"#mobile/*": ["./src/*"]
},
"strict": true
},
"extends": "expo/tsconfig.base",
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}
+10 -3
View File
@@ -37,7 +37,7 @@ BASANGO_API_CRAWLER_TOKEN=replace-with-the-same-secret-as-the-api
The endpoint is the API origin without `/ingest`; the crawler appends `/ingest/articles` and the
other ingestion paths itself. The token must exactly match `BASANGO_API_CRAWLER_TOKEN` in the
TypeScript API's root `.env`. Keep both values secret. Before Certbot enables HTTPS, use
TypeScript API's root `.env.local`. Keep both values secret. Before Certbot enables HTTPS, use
`http://api.basango.ngandu.dev` only for an initial connectivity check, then switch the crawler to
the HTTPS endpoint.
@@ -59,10 +59,17 @@ pm2 startup
pm2 save
```
The root `.env` must provide the API database, encryption, crawler token, and Better Auth secrets.
Use these public authentication values:
The committed root `.env` only contains safe development examples. On the server, create an ignored
root `.env.prod` for production-wide values:
```dotenv
VITE_PUBLIC_API_URL=https://api.basango.ngandu.dev
VITE_PUBLIC_URL=https://dashboard.basango.ngandu.dev
BETTER_AUTH_URL=https://api.basango.ngandu.dev
BETTER_AUTH_COOKIE_DOMAIN=.basango.ngandu.dev
```
Put the API database, encryption key, crawler token, and Better Auth secret in root `.env.local`.
That file is loaded after `.env.prod`, so it is the final file-based override and stays outside Git.
Variables injected directly by PM2 or the host still take precedence. See the
[environment configuration guide](environment.md) for the complete convention.
+61
View File
@@ -0,0 +1,61 @@
# Environment configuration
All TypeScript applications read environment files from the repository root. Do not create
application-specific environment files under `apps/` or `packages/`.
## Files and precedence
Environment files are loaded from lowest to highest priority:
1. `.env` contains committed, safe development defaults and documents the available variables.
2. `.env.dev`, `.env.test`, or `.env.prod` overrides `.env` for the selected environment.
3. `.env.local` overrides every environment file and is reserved for machine-local values and
secrets.
4. Variables already supplied by the process, CI system, or process manager override all files.
Only `.env` is committed. Every `.env.*` file is ignored by Git. Never put production credentials
or personal secrets in `.env`.
`NODE_ENV` accepts exactly `dev`, `test`, or `prod`, selecting `.env.dev`, `.env.test`, or
`.env.prod` respectively. The longer `development` and `production` values are intentionally
rejected so every TypeScript application uses the same vocabulary.
The API, Drizzle commands, and dashboard build share this ordering. The dashboard exposes only
variables prefixed with `VITE_` to browser code. If the mobile application gains public runtime
configuration, use `EXPO_PUBLIC_` variables and keep them in these same root files.
## Local development
The committed `.env` is enough for the default local Docker services. Put personal overrides in
`.env.local`:
```dotenv
BASANGO_API_CRAWLER_TOKEN=my-local-token
```
Use `.env.dev` when an override should apply to every developer on one machine but should still be
superseded by `.env.local`.
## Production
On the server, set `NODE_ENV=prod` and create `.env.prod` for production-wide values. Put
host-specific credentials in `.env.local`. For example:
```dotenv
# .env.prod
VITE_PUBLIC_API_URL=https://api.basango.ngandu.dev
VITE_PUBLIC_URL=https://dashboard.basango.ngandu.dev
BETTER_AUTH_URL=https://api.basango.ngandu.dev
BETTER_AUTH_COOKIE_DOMAIN=.basango.ngandu.dev
```
```dotenv
# .env.local
BASANGO_API_CRAWLER_TOKEN=replace-with-a-long-random-secret
BASANGO_DATABASE_URL=postgresql://...
BASANGO_ENCRYPTION_KEY=replace-with-a-64-character-key
BETTER_AUTH_SECRET=replace-with-a-long-random-secret
```
The crawler uses the same ingestion token as the API but is configured in the crawler deployment,
not in this TypeScript repository.
+4
View File
@@ -19,6 +19,10 @@ substantive Basango-owned behavior.
Keep route composition, navigation, environment access, and platform-specific behavior in the owning application.
Start new product behavior there. Extract it only after a stable package responsibility is visible.
Environment files are an exception to application ownership: all TypeScript applications use the repository-root
precedence documented in the [environment configuration guide](../environment.md). Application-specific `.env` files
must not be added under `apps/` or `packages/`.
## Shared packages
- `@basango/domain` owns runtime schemas and inferred TypeScript contracts shared across process boundaries. It must
+12 -22
View File
@@ -1,31 +1,16 @@
const path = require("node:path");
const sharedEnv = {
NODE_ENV: "production",
};
const apiEnv = {
...sharedEnv,
BASANGO_API_HOST: "127.0.0.1",
BASANGO_API_PORT: "3080",
};
const dashboardEnv = {
...sharedEnv,
HOST: "127.0.0.1",
PORT: "3000",
VITE_PUBLIC_API_URL: "https://api.basango.ngandu.dev",
VITE_PUBLIC_URL: "https://dashboard.basango.ngandu.dev",
};
module.exports = {
apps: [
{
args: "run start",
autorestart: true,
cwd: path.join(__dirname, "apps", "api"),
env: apiEnv,
env_production: apiEnv,
env: {
BASANGO_API_HOST: "127.0.0.1",
BASANGO_API_PORT: "3080",
NODE_ENV: "prod",
},
max_restarts: 5,
name: "api.basango.ngandu.dev",
script: "bun",
@@ -35,8 +20,13 @@ module.exports = {
args: "run start",
autorestart: true,
cwd: path.join(__dirname, "apps", "dashboard"),
env: dashboardEnv,
env_production: dashboardEnv,
env: {
HOST: "127.0.0.1",
NODE_ENV: "prod",
PORT: "3000",
VITE_PUBLIC_API_URL: "https://api.basango.ngandu.dev",
VITE_PUBLIC_URL: "https://dashboard.basango.ngandu.dev",
},
max_restarts: 5,
name: "dashboard.basango.ngandu.dev",
script: "bun",
-1
View File
@@ -1 +0,0 @@
{ "dialect": "postgresql", "entries": [], "version": "7" }
+2 -2
View File
@@ -1,8 +1,8 @@
import { readEnvFile } from "@basango/domain/config/environment";
import { readEnvFiles } from "@basango/domain/config/environment";
import { defineConfig } from "drizzle-kit";
const databaseUrl = (
process.env.BASANGO_DATABASE_URL ?? readEnvFile().BASANGO_DATABASE_URL
process.env.BASANGO_DATABASE_URL ?? readEnvFiles().BASANGO_DATABASE_URL
)?.trim();
if (!databaseUrl) {
+1 -1
View File
@@ -4,7 +4,7 @@ import { Pool } from "pg";
import * as schema from "#db/schema";
const isDevelopment = env.NODE_ENV === "development";
const isDevelopment = env.NODE_ENV === "dev";
const pool = new Pool({
allowExitOnIdle: true,
@@ -0,0 +1,90 @@
import { afterEach, describe, expect, test } from "bun:test";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { normalizeNodeEnvironment, readEnvFiles, resolveEnvFiles } from "./environment";
const temporaryDirectories: string[] = [];
afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
fs.rmSync(directory, { force: true, recursive: true });
}
});
describe("normalizeNodeEnvironment", () => {
test.each([
["dev", "dev"],
["test", "test"],
["prod", "prod"],
] as const)("normalizes %s to %s", (value, expected) => {
expect(normalizeNodeEnvironment(value)).toBe(expected);
});
test.each(["development", "production"])("rejects unsupported value %s", (value) => {
expect(() => normalizeNodeEnvironment(value)).toThrow(`Unsupported NODE_ENV: ${value}`);
});
});
describe("environment files", () => {
test("loads base, mode, then local values", () => {
const directory = createEnvironmentDirectory({
".env": "VALUE=base\nBASE_ONLY=yes\n",
".env.local": "VALUE=local\nLOCAL_ONLY=yes\n",
".env.prod": "VALUE=production\nMODE_ONLY=yes\n",
});
expect(readEnvFiles({ cwd: directory, nodeEnvironment: "prod" })).toEqual({
BASE_ONLY: "yes",
LOCAL_ONLY: "yes",
MODE_ONLY: "yes",
VALUE: "local",
});
});
test.each([
["dev", ".env.dev"],
["test", ".env.test"],
["prod", ".env.prod"],
] as const)("selects %s mode file", (mode, expectedFile) => {
const directory = createEnvironmentDirectory({ ".env": "VALUE=base\n" });
const files = resolveEnvFiles({ cwd: directory, nodeEnvironment: mode });
expect(files.map((file) => path.basename(file.path))).toEqual([
".env",
expectedFile,
".env.local",
]);
});
test("uses NODE_ENV from the base file when no mode is provided", () => {
const directory = createEnvironmentDirectory({
".env": "NODE_ENV=prod\nVALUE=base\n",
".env.prod": "VALUE=production\n",
});
const previousNodeEnvironment = process.env.NODE_ENV;
delete process.env.NODE_ENV;
try {
expect(readEnvFiles({ cwd: directory }).VALUE).toBe("production");
} finally {
if (previousNodeEnvironment === undefined) {
delete process.env.NODE_ENV;
} else {
process.env.NODE_ENV = previousNodeEnvironment;
}
}
});
});
function createEnvironmentDirectory(files: Record<string, string>): string {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "basango-env-"));
temporaryDirectories.push(directory);
for (const [fileName, content] of Object.entries(files)) {
fs.writeFileSync(path.join(directory, fileName), content);
}
return directory;
}
+75 -8
View File
@@ -2,13 +2,41 @@ import fs from "node:fs";
import path from "node:path";
import { parseEnv } from "node:util";
export const findEnvPath = (): string => {
export type NodeEnvironment = "dev" | "test" | "prod";
export interface EnvironmentFileOptions {
cwd?: string;
envPath?: string;
nodeEnvironment?: string;
}
interface EnvironmentFile {
optional: boolean;
path: string;
}
export function normalizeNodeEnvironment(value?: string): NodeEnvironment {
switch (value?.trim().toLowerCase()) {
case undefined:
case "":
case "dev":
return "dev";
case "test":
return "test";
case "prod":
return "prod";
default:
throw new Error(`Unsupported NODE_ENV: ${value}`);
}
}
export function findEnvPath(cwd = process.cwd()): string {
const configured = process.env.BASANGO_ENV_PATH?.trim();
if (configured) {
return path.resolve(configured);
}
let current = process.cwd();
let current = path.resolve(cwd);
while (true) {
const candidate = path.join(current, ".env");
if (fs.existsSync(candidate)) {
@@ -17,16 +45,55 @@ export const findEnvPath = (): string => {
const parent = path.dirname(current);
if (parent === current) {
return path.join(process.cwd(), ".env");
return path.join(path.resolve(cwd), ".env");
}
current = parent;
}
};
}
export const readEnvFile = (envPath = findEnvPath()): Record<string, string | undefined> => {
if (!fs.existsSync(envPath)) {
export function resolveEnvFiles(options: EnvironmentFileOptions = {}): EnvironmentFile[] {
const envPath = path.resolve(options.envPath ?? findEnvPath(options.cwd));
const baseEnvironment = readOptionalEnvFile(envPath);
const nodeEnvironment = normalizeNodeEnvironment(
options.nodeEnvironment ?? process.env.NODE_ENV ?? baseEnvironment.NODE_ENV,
);
const directory = path.dirname(envPath);
const paths = [
envPath,
path.join(directory, `.env.${nodeEnvironment}`),
path.join(directory, ".env.local"),
];
return [...new Set(paths)].map((filePath, index) => ({
optional: index > 0,
path: filePath,
}));
}
export function readEnvFiles(
options: EnvironmentFileOptions = {},
): Record<string, string | undefined> {
const environment: Record<string, string | undefined> = {};
for (const file of resolveEnvFiles(options)) {
if (!fs.existsSync(file.path)) {
if (file.optional) {
continue;
}
throw new Error(`Environment file not found: ${file.path}`);
}
Object.assign(environment, parseEnv(fs.readFileSync(file.path, "utf8")));
}
return environment;
}
function readOptionalEnvFile(filePath: string): Record<string, string | undefined> {
if (!fs.existsSync(filePath)) {
return {};
}
return parseEnv(fs.readFileSync(envPath, "utf8"));
};
return parseEnv(fs.readFileSync(filePath, "utf8"));
}
+5 -3
View File
@@ -6,7 +6,7 @@ import z from "zod";
import { ApiConfigurationSchema } from "./api";
import { DatabaseConfigurationSchema } from "./database";
import { EncryptionConfigurationSchema } from "./encryption";
import { findEnvPath } from "./environment";
import { resolveEnvFiles } from "./environment";
import { LoggerConfigurationSchema } from "./logger";
import { SharedConfigurationSchema } from "./shared";
@@ -16,10 +16,12 @@ export * from "./encryption";
export * from "./logger";
export * from "./shared";
const nodeEnvironmentSchema = z.enum(["dev", "test", "prod"]).default("dev");
export const { env, config } = await defineConfig({
cwd: fileURLToPath(new URL("../..", import.meta.url)),
environment: {
files: [{ optional: true, path: findEnvPath() }],
files: resolveEnvFiles(),
redact: [
"BASANGO_ADMIN_PASSWORD",
"BASANGO_API_CRAWLER_TOKEN",
@@ -64,7 +66,7 @@ export const { env, config } = await defineConfig({
BETTER_AUTH_COOKIE_DOMAIN: z.string().optional(),
BETTER_AUTH_SECRET: z.string().optional(),
BETTER_AUTH_URL: z.string().optional(),
NODE_ENV: z.enum(["development", "test", "production"]).optional(),
NODE_ENV: nodeEnvironmentSchema,
}),
},
schema: z.object({
+1 -1
View File
@@ -98,7 +98,7 @@ const createPinoLogger = (): LoggerLike => {
return createLogger({
level: config.logger.level,
...(env.NODE_ENV !== "production" &&
...(env.NODE_ENV !== "prod" &&
env.BASANGO_LOGGER_PRETTY === "true" && {
transport: {
options: {
+4 -2
View File
@@ -1,6 +1,8 @@
{
"$schema": "https://turborepo.com/schema.json",
"globalDependencies": ["**/.env"],
"globalDependencies": [".env", ".env.*"],
"globalEnv": ["NODE_ENV", "VITE_*", "EXPO_PUBLIC_*", "VERCEL_TARGET_ENV", "VERCEL_URL"],
"globalPassThroughEnv": ["BASANGO_*", "BETTER_AUTH_*"],
"tasks": {
"build": {
"dependsOn": ["^build"],
@@ -14,7 +16,7 @@
},
"dev": {
"cache": false,
"inputs": ["$TURBO_DEFAULT$", ".env"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"persistent": true
},
"start": {