Remove dotenv and inject runtime env
This commit is contained in:
1
bun.lock
1
bun.lock
@@ -514,7 +514,6 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"detect-content-type": "^1.2.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "catalog:",
|
||||
"express": "^4.21.1",
|
||||
"express-rate-limit": "^7.5.1",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "dotenv/config";
|
||||
import { loadLocalEnv } from "../src/utils/envUtils";
|
||||
|
||||
loadLocalEnv();
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"detect-content-type": "^1.2.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"drizzle-orm": "catalog:",
|
||||
"express": "^4.21.1",
|
||||
"express-rate-limit": "^7.5.1",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "dotenv/config";
|
||||
import { loadLocalEnv } from "./src/utils/envUtils";
|
||||
import Stripe from "stripe";
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import "../sentry.ts";
|
||||
import { CronJob } from "cron";
|
||||
import { initDrizzle } from "../db/initDrizzle.js";
|
||||
@@ -37,7 +38,7 @@ const logCronHeartbeat = () => {
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
if (process.env.DISABLE_CRON === "true") {
|
||||
if (runtimeEnv.DISABLE_CRON === "true") {
|
||||
console.log(`Cron disabled!`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { getTableColumns, type SQL, sql } from "drizzle-orm";
|
||||
import type { PgTable } from "drizzle-orm/pg-core";
|
||||
|
||||
@@ -115,7 +116,7 @@ export const isConnectionDropError = ({
|
||||
};
|
||||
|
||||
/** Throws if the connection string looks like a production database. Single source of truth for this check. */
|
||||
export const assertNotProductionDb = (url = process.env.DATABASE_URL || "") => {
|
||||
export const assertNotProductionDb = (url = runtimeEnv.DATABASE_URL || "") => {
|
||||
if (url.includes("us-east-2")) {
|
||||
throw new Error(
|
||||
"Refusing to run against production database (connection string contains us-east-2)",
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { schemas as schema } from "@autumn/shared";
|
||||
import { instrumentDrizzleClient } from "@kubiks/otel-drizzle";
|
||||
|
||||
@@ -58,8 +55,8 @@ export const initDrizzle = ({
|
||||
name?: string;
|
||||
} = {}) => {
|
||||
const envDbUrl = replica
|
||||
? process.env.DATABASE_REPLICA_URL
|
||||
: process.env.DATABASE_URL;
|
||||
? runtimeEnv.DATABASE_REPLICA_URL
|
||||
: runtimeEnv.DATABASE_URL;
|
||||
|
||||
const dbUrl = databaseUrl || envDbUrl || "";
|
||||
|
||||
@@ -96,7 +93,7 @@ export const initDrizzle = ({
|
||||
};
|
||||
|
||||
// Strict latency limits in prod; relaxed locally so dev pool warm-up doesn't kill tests.
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const isProd = runtimeEnv.NODE_ENV === "production";
|
||||
|
||||
const poolMaxFromEnv = ({
|
||||
envVar,
|
||||
@@ -105,7 +102,7 @@ const poolMaxFromEnv = ({
|
||||
envVar: string;
|
||||
fallback: number;
|
||||
}): number => {
|
||||
const parsed = Number(process.env[envVar]);
|
||||
const parsed = Number(runtimeEnv[envVar]);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
||||
};
|
||||
|
||||
@@ -151,7 +148,7 @@ export const { db: dbCritical, client: clientCritical } = initDrizzle({
|
||||
name: "critical",
|
||||
maxConnections: criticalPoolMax,
|
||||
connectTimeout: isProd ? 2 : 30,
|
||||
databaseUrl: process.env.DATABASE_CRITICAL_URL,
|
||||
databaseUrl: runtimeEnv.DATABASE_CRITICAL_URL,
|
||||
poolConfig: {
|
||||
application_name: "autumn-critical",
|
||||
query_timeout: isProd ? 2_000 : 30_000,
|
||||
@@ -169,7 +166,7 @@ export const { db: dbGeneral, client: clientGeneral } = initDrizzle({
|
||||
|
||||
// -- Replica pool: used as fallback when primary is degraded --
|
||||
// Only created if DATABASE_REPLICA_URL is configured.
|
||||
const replicaResult = process.env.DATABASE_REPLICA_URL
|
||||
const replicaResult = runtimeEnv.DATABASE_REPLICA_URL
|
||||
? initDrizzle({
|
||||
name: "replica",
|
||||
replica: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import type { Pool } from "pg";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
@@ -243,7 +244,7 @@ export const getPgHealthState = (): {
|
||||
failureCount,
|
||||
probeActive: probeInterval !== null,
|
||||
firstProbeSuccessAt,
|
||||
hasReplica: !!process.env.DATABASE_REPLICA_URL,
|
||||
hasReplica: !!runtimeEnv.DATABASE_REPLICA_URL,
|
||||
});
|
||||
|
||||
/** Force DEGRADED state (for testing). Does NOT start the recovery probe. */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { Pool } from "pg";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
|
||||
@@ -11,8 +12,8 @@ const registry = new Map<string, RegisteredPool>();
|
||||
let snapshotInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const getRole = (): string => {
|
||||
if (process.env.WORKER === "true") return "worker";
|
||||
if (process.env.CRON === "true") return "cron";
|
||||
if (runtimeEnv.WORKER === "true") return "worker";
|
||||
if (runtimeEnv.CRON === "true") return "cron";
|
||||
return "http";
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const hash = (value: string) =>
|
||||
@@ -33,9 +34,9 @@ export const redactDatabaseUrl = (databaseUrl?: string) => {
|
||||
};
|
||||
|
||||
export const getRedactedDatabaseUrls = () => ({
|
||||
primary: redactDatabaseUrl(process.env.DATABASE_URL),
|
||||
replica: redactDatabaseUrl(process.env.DATABASE_REPLICA_URL),
|
||||
primary: redactDatabaseUrl(runtimeEnv.DATABASE_URL),
|
||||
replica: redactDatabaseUrl(runtimeEnv.DATABASE_REPLICA_URL),
|
||||
critical: redactDatabaseUrl(
|
||||
process.env.DATABASE_CRITICAL_URL || process.env.DATABASE_URL,
|
||||
runtimeEnv.DATABASE_CRITICAL_URL || runtimeEnv.DATABASE_URL,
|
||||
),
|
||||
});
|
||||
|
||||
5
server/src/external/ai/initAi.ts
vendored
5
server/src/external/ai/initAi.ts
vendored
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
|
||||
export const anthropicClient = process.env.ANTHROPIC_API_KEY
|
||||
export const anthropicClient = runtimeEnv.ANTHROPIC_API_KEY
|
||||
? createAnthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
apiKey: runtimeEnv.ANTHROPIC_API_KEY,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
19
server/src/external/autumn/autumnCli.ts
vendored
19
server/src/external/autumn/autumnCli.ts
vendored
@@ -1,8 +1,5 @@
|
||||
/** biome-ignore-all lint/suspicious/noExplicitAny: AutumnInt is used for internal testing & scripts */
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
type ApiBaseEntity,
|
||||
type ApiCusFeatureV3,
|
||||
@@ -93,9 +90,9 @@ export class AutumnInt {
|
||||
liveUrl?: boolean;
|
||||
skipCacheDeletion?: boolean;
|
||||
} = {}) {
|
||||
// this.apiKey = apiKey || process.env.AUTUMN_API_KEY || "";
|
||||
// this.apiKey = apiKey || runtimeEnv.AUTUMN_API_KEY || "";
|
||||
this.apiKey =
|
||||
apiKey || secretKey || process.env.UNIT_TEST_AUTUMN_SECRET_KEY || "";
|
||||
apiKey || secretKey || runtimeEnv.UNIT_TEST_AUTUMN_SECRET_KEY || "";
|
||||
|
||||
this.headers = {
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
@@ -110,7 +107,7 @@ export class AutumnInt {
|
||||
this.headers["org-config"] = JSON.stringify(orgConfig);
|
||||
}
|
||||
|
||||
const envBase = process.env.AUTUMN_TEST_BASE_URL;
|
||||
const envBase = runtimeEnv.AUTUMN_TEST_BASE_URL;
|
||||
const envBaseUrl = envBase ? `${envBase.replace(/\/$/, "")}/v1` : null;
|
||||
this.baseUrl =
|
||||
baseUrl ||
|
||||
@@ -316,7 +313,7 @@ export class AutumnInt {
|
||||
Object.keys(headers).length > 0 ? headers : undefined,
|
||||
);
|
||||
|
||||
const concurrency = Number(process.env.TEST_FILE_CONCURRENCY || "0");
|
||||
const concurrency = Number(runtimeEnv.TEST_FILE_CONCURRENCY || "0");
|
||||
const defaultTimeout = concurrency > 1 ? 5000 : 4000;
|
||||
const finalTimeout = timeout ?? defaultTimeout;
|
||||
if (finalTimeout) {
|
||||
@@ -1219,7 +1216,7 @@ export class AutumnInt {
|
||||
Object.keys(headers).length > 0 ? headers : undefined,
|
||||
);
|
||||
|
||||
const concurrency = Number(process.env.TEST_FILE_CONCURRENCY || "0");
|
||||
const concurrency = Number(runtimeEnv.TEST_FILE_CONCURRENCY || "0");
|
||||
const defaultTimeout = concurrency > 1 ? 5000 : 4000;
|
||||
const finalTimeout = timeout ?? defaultTimeout;
|
||||
if (finalTimeout) {
|
||||
@@ -1257,7 +1254,7 @@ export class AutumnInt {
|
||||
): Promise<TResponse> => {
|
||||
const data = await this.post(`/billing.create_schedule`, params);
|
||||
|
||||
const concurrency = Number(process.env.TEST_FILE_CONCURRENCY || "0");
|
||||
const concurrency = Number(runtimeEnv.TEST_FILE_CONCURRENCY || "0");
|
||||
const defaultTimeout = concurrency > 1 ? 5000 : 4000;
|
||||
const finalTimeout = timeout ?? defaultTimeout;
|
||||
if (finalTimeout) {
|
||||
@@ -1288,7 +1285,7 @@ export class AutumnInt {
|
||||
Object.keys(headers).length > 0 ? headers : undefined,
|
||||
);
|
||||
|
||||
const concurrency = Number(process.env.TEST_FILE_CONCURRENCY || "0");
|
||||
const concurrency = Number(runtimeEnv.TEST_FILE_CONCURRENCY || "0");
|
||||
const defaultTimeout = concurrency > 1 ? 5000 : 4000;
|
||||
const finalTimeout = timeout ?? defaultTimeout;
|
||||
if (finalTimeout) {
|
||||
|
||||
9
server/src/external/autumn/autumnRpcCli.ts
vendored
9
server/src/external/autumn/autumnRpcCli.ts
vendored
@@ -1,8 +1,5 @@
|
||||
/** biome-ignore-all lint/suspicious/noExplicitAny: RPC test client needs flexible payload typing */
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, type OrgConfig } from "@autumn/shared";
|
||||
import AutumnError from "./autumnCli.js";
|
||||
|
||||
@@ -27,7 +24,7 @@ export class AutumnRpcCli {
|
||||
liveUrl?: boolean;
|
||||
} = {}) {
|
||||
this.apiKey =
|
||||
apiKey || secretKey || process.env.UNIT_TEST_AUTUMN_SECRET_KEY || "";
|
||||
apiKey || secretKey || runtimeEnv.UNIT_TEST_AUTUMN_SECRET_KEY || "";
|
||||
|
||||
this.headers = {
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
@@ -42,7 +39,7 @@ export class AutumnRpcCli {
|
||||
this.headers["org-config"] = JSON.stringify(orgConfig);
|
||||
}
|
||||
|
||||
const envBase = process.env.AUTUMN_TEST_BASE_URL;
|
||||
const envBase = runtimeEnv.AUTUMN_TEST_BASE_URL;
|
||||
const envBaseUrl = envBase ? `${envBase.replace(/\/$/, "")}/v1` : null;
|
||||
this.baseUrl =
|
||||
baseUrl ||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { Hono } from "hono";
|
||||
import { Webhook } from "svix";
|
||||
@@ -17,7 +18,7 @@ const verifyAutumnWebhook = async ({
|
||||
svixSignature: string | undefined;
|
||||
};
|
||||
}) => {
|
||||
const wh = new Webhook(process.env.AUTUMN_WEBHOOK_SECRET!);
|
||||
const wh = new Webhook(runtimeEnv.AUTUMN_WEBHOOK_SECRET!);
|
||||
|
||||
const { svixId, svixTimestamp, svixSignature } = headers;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
@@ -55,9 +56,9 @@ export const resolveAwsTaskIdentity = async (): Promise<AwsTaskIdentity> => {
|
||||
|
||||
identityPromise = (async (): Promise<AwsTaskIdentity> => {
|
||||
const imageSha =
|
||||
process.env.FC_GIT_COMMIT_SHA || process.env.IMAGE_TAG || null;
|
||||
runtimeEnv.FC_GIT_COMMIT_SHA || runtimeEnv.IMAGE_TAG || null;
|
||||
|
||||
const metadataUri = process.env.ECS_CONTAINER_METADATA_URI_V4;
|
||||
const metadataUri = runtimeEnv.ECS_CONTAINER_METADATA_URI_V4;
|
||||
let serviceArn: string | null = null;
|
||||
|
||||
if (metadataUri) {
|
||||
@@ -94,7 +95,7 @@ export const resolveAwsTaskIdentity = async (): Promise<AwsTaskIdentity> => {
|
||||
`[awsTaskIdentity] ECS metadata fetch failed: ${error instanceof Error ? error.message : error}; gate will fail open`,
|
||||
);
|
||||
}
|
||||
} else if (process.env.NODE_ENV === "production") {
|
||||
} else if (runtimeEnv.NODE_ENV === "production") {
|
||||
console.warn(
|
||||
"[awsTaskIdentity] ECS_CONTAINER_METADATA_URI_V4 unset in production — gate will fail open",
|
||||
);
|
||||
|
||||
3
server/src/external/aws/ecs/onAwsEcs.ts
vendored
3
server/src/external/aws/ecs/onAwsEcs.ts
vendored
@@ -5,5 +5,6 @@
|
||||
* so it's the canonical "am I on AWS?" gate — same one
|
||||
* `awsTaskIdentity` uses to discover the running service.
|
||||
*/
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
export const onAwsEcs = (): boolean =>
|
||||
Boolean(process.env.ECS_CONTAINER_METADATA_URI_V4);
|
||||
Boolean(runtimeEnv.ECS_CONTAINER_METADATA_URI_V4);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
CreateScheduleCommand,
|
||||
DeleteScheduleCommand,
|
||||
@@ -8,14 +9,14 @@ import { extractLocalEndpoint } from "@/queue/initSqs.js";
|
||||
import { schedulerClient } from "./initEventBridge.js";
|
||||
|
||||
const isLocalQueue = (): boolean =>
|
||||
!!extractLocalEndpoint({ queueUrl: process.env.SQS_QUEUE_URL_V2 });
|
||||
!!extractLocalEndpoint({ queueUrl: runtimeEnv.SQS_QUEUE_URL_V2 });
|
||||
|
||||
const SCHEDULE_GROUP = "default";
|
||||
const SCHEDULER_ROLE_ARN = process.env.AWS_EVENTBRIDGE_SCHEDULER_ROLE_ARN || "";
|
||||
const SCHEDULER_ROLE_ARN = runtimeEnv.AWS_EVENTBRIDGE_SCHEDULER_ROLE_ARN || "";
|
||||
|
||||
/** Derives SQS ARN from URL: https://sqs.<region>.amazonaws.com/<account>/<name> -> arn:aws:sqs:<region>:<account>:<name> */
|
||||
const getSqsQueueArn = (): string => {
|
||||
const url = process.env.SQS_QUEUE_URL_V2 || "";
|
||||
const url = runtimeEnv.SQS_QUEUE_URL_V2 || "";
|
||||
const match = url.match(
|
||||
/^https:\/\/sqs\.([a-z0-9-]+)\.amazonaws\.com\/(\d+)\/(.+)$/,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { SchedulerClient } from "@aws-sdk/client-scheduler";
|
||||
import {
|
||||
DEFAULT_AWS_REGION,
|
||||
@@ -7,11 +8,11 @@ import {
|
||||
const getSchedulerClientConfig = () => ({
|
||||
region:
|
||||
extractRegionFromQueueUrl({
|
||||
queueUrl: process.env.SQS_QUEUE_URL_V2,
|
||||
queueUrl: runtimeEnv.SQS_QUEUE_URL_V2,
|
||||
}) || DEFAULT_AWS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID || "",
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "",
|
||||
accessKeyId: runtimeEnv.AWS_ACCESS_KEY_ID || "",
|
||||
secretAccessKey: runtimeEnv.AWS_SECRET_ACCESS_KEY || "",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
5
server/src/external/aws/s3/adminS3Config.ts
vendored
5
server/src/external/aws/s3/adminS3Config.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
export const ADMIN_REQUEST_BLOCK_CONFIG_KEY = "admin/request-block-config.json";
|
||||
export const ADMIN_ROLLOUT_CONFIG_KEY = "admin/rollout-config.json";
|
||||
export const ADMIN_FEATURE_FLAGS_CONFIG_KEY = "admin/feature-flags-config.json";
|
||||
@@ -21,8 +22,8 @@ export const BLUE_GREEN_CRON_ACTIVE_SLOT_KEY =
|
||||
"admin/blue-green-cron-active-slot.json";
|
||||
export const BLUE_GREEN_HEARTBEAT_KEY_PREFIX = "admin/blue-green-heartbeats";
|
||||
|
||||
const bucket = process.env.S3_BUCKET || "autumn-prod-server";
|
||||
const region = process.env.S3_REGION || "us-east-2";
|
||||
const bucket = runtimeEnv.S3_BUCKET || "autumn-prod-server";
|
||||
const region = runtimeEnv.S3_REGION || "us-east-2";
|
||||
|
||||
export const getAdminS3Config = () => {
|
||||
return {
|
||||
|
||||
5
server/src/external/axiom/initAxiom.ts
vendored
5
server/src/external/axiom/initAxiom.ts
vendored
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { Axiom } from "@axiomhq/js";
|
||||
|
||||
const AXIOM_ADMIN_TOKEN = process.env.AXIOM_ADMIN_TOKEN;
|
||||
const AXIOM_ORG_ID = process.env.AXIOM_ORG_ID;
|
||||
const AXIOM_ADMIN_TOKEN = runtimeEnv.AXIOM_ADMIN_TOKEN;
|
||||
const AXIOM_ORG_ID = runtimeEnv.AXIOM_ORG_ID;
|
||||
|
||||
export const axiomClient: Axiom | null = AXIOM_ADMIN_TOKEN
|
||||
? new Axiom({
|
||||
|
||||
5
server/src/external/connect/connectUtils.ts
vendored
5
server/src/external/connect/connectUtils.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { AppEnv, InternalError, type Organization } from "@autumn/shared";
|
||||
import type { DrizzleCli } from "@server/db/initDrizzle.js";
|
||||
import { OrgService } from "@server/internal/orgs/OrgService.js";
|
||||
@@ -42,8 +43,8 @@ export const deauthorizeAccount = async ({
|
||||
await masterStripe.oauth.deauthorize({
|
||||
client_id:
|
||||
env === AppEnv.Live
|
||||
? process.env.STRIPE_LIVE_CLIENT_ID || ""
|
||||
: process.env.STRIPE_SANDBOX_CLIENT_ID || "",
|
||||
? runtimeEnv.STRIPE_LIVE_CLIENT_ID || ""
|
||||
: runtimeEnv.STRIPE_SANDBOX_CLIENT_ID || "",
|
||||
stripe_user_id: accountId,
|
||||
});
|
||||
logger.info(`Deauthorized account ${accountId} for ${env}`);
|
||||
|
||||
18
server/src/external/connect/initStripeCli.ts
vendored
18
server/src/external/connect/initStripeCli.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
AppEnv,
|
||||
InternalError,
|
||||
@@ -6,7 +7,6 @@ import {
|
||||
} from "@autumn/shared";
|
||||
import { decryptData } from "@server/utils/encryptUtils.js";
|
||||
import { instrumentStripe } from "@server/utils/otel/instrumentStripe.js";
|
||||
import "dotenv/config";
|
||||
import type { DrizzleCli } from "@server/db/initDrizzle.js";
|
||||
import Stripe from "stripe";
|
||||
import {
|
||||
@@ -25,19 +25,19 @@ export const initMasterStripe = (params?: {
|
||||
let secretKey: string;
|
||||
|
||||
if (params?.env === AppEnv.Live) {
|
||||
if (!process.env.STRIPE_LIVE_SECRET_KEY) {
|
||||
if (!runtimeEnv.STRIPE_LIVE_SECRET_KEY) {
|
||||
throw new InternalError({
|
||||
message: "STRIPE_LIVE_SECRET_KEY env variable is not set",
|
||||
});
|
||||
}
|
||||
secretKey = process.env.STRIPE_LIVE_SECRET_KEY;
|
||||
secretKey = runtimeEnv.STRIPE_LIVE_SECRET_KEY;
|
||||
} else {
|
||||
if (!process.env.STRIPE_SANDBOX_SECRET_KEY) {
|
||||
if (!runtimeEnv.STRIPE_SANDBOX_SECRET_KEY) {
|
||||
throw new InternalError({
|
||||
message: "STRIPE_SANDBOX_SECRET_KEY env variable is not set",
|
||||
});
|
||||
}
|
||||
secretKey = process.env.STRIPE_SANDBOX_SECRET_KEY;
|
||||
secretKey = runtimeEnv.STRIPE_SANDBOX_SECRET_KEY;
|
||||
}
|
||||
|
||||
const cacheKey = buildMasterCacheKey({
|
||||
@@ -56,7 +56,9 @@ export const initMasterStripe = (params?: {
|
||||
? ("2025-02-24.acacia" as any)
|
||||
: undefined,
|
||||
});
|
||||
return params?.skipInstrumentation ? client : instrumentStripe({ client });
|
||||
return params?.skipInstrumentation
|
||||
? client
|
||||
: instrumentStripe({ client });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -136,9 +138,9 @@ export const getStripeWebhookSecret = async ({
|
||||
|
||||
let secret: string;
|
||||
if (env === AppEnv.Live) {
|
||||
secret = process.env.STRIPE_LIVE_WEBHOOK_SECRET || "";
|
||||
secret = runtimeEnv.STRIPE_LIVE_WEBHOOK_SECRET || "";
|
||||
} else {
|
||||
secret = process.env.STRIPE_SANDBOX_WEBHOOK_SECRET || "";
|
||||
secret = runtimeEnv.STRIPE_SANDBOX_WEBHOOK_SECRET || "";
|
||||
}
|
||||
|
||||
if (!secret) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type Stripe from "stripe";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { OrgService } from "@/internal/orgs/OrgService.js";
|
||||
@@ -15,7 +16,7 @@ export const registerConnectWebhook = async ({
|
||||
const stripeCli = initPlatformStripe({ masterOrg: org, env });
|
||||
|
||||
const curWebhookEndpoints = await stripeCli.webhookEndpoints.list();
|
||||
const backendUrl = process.env.SERVER_URL || process.env.STRIPE_WEBHOOK_URL;
|
||||
const backendUrl = runtimeEnv.SERVER_URL || runtimeEnv.STRIPE_WEBHOOK_URL;
|
||||
|
||||
const webhookUrl = `${backendUrl}/webhooks/connect/${env}?org_id=${org.id}`;
|
||||
|
||||
|
||||
3
server/src/external/hatchet/initHatchet.ts
vendored
3
server/src/external/hatchet/initHatchet.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { HatchetClient } from "@hatchet-dev/typescript-sdk/v1";
|
||||
|
||||
export const isHatchetEnabled = !!process.env.HATCHET_CLIENT_TOKEN;
|
||||
export const isHatchetEnabled = !!runtimeEnv.HATCHET_CLIENT_TOKEN;
|
||||
|
||||
export const hatchet = isHatchetEnabled ? HatchetClient.init() : null;
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
* time via `syncEnvVars` to push secrets to the cloud env. Kept SDK-free
|
||||
* so trigger.config.ts can import it without bloating the build.
|
||||
*
|
||||
* Runtime code uses `initInfisical` (SDK-based, populates process.env).
|
||||
* Runtime code uses `initInfisical` (SDK-based, populates runtimeEnv).
|
||||
*/
|
||||
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
export type InfisicalSyncEnvVar = { name: string; value: string };
|
||||
|
||||
export type FetchInfisicalSecretsArgs = {
|
||||
@@ -106,10 +107,10 @@ export const fetchInfisicalSecretsFromEnv = (
|
||||
ctxEnv: Record<string, string | undefined> = {},
|
||||
): Promise<InfisicalSyncEnvVar[]> =>
|
||||
fetchInfisicalSecrets({
|
||||
clientId: process.env.INFISICAL_CLIENT_ID ?? ctxEnv.INFISICAL_CLIENT_ID,
|
||||
clientId: runtimeEnv.INFISICAL_CLIENT_ID ?? ctxEnv.INFISICAL_CLIENT_ID,
|
||||
clientSecret:
|
||||
process.env.INFISICAL_CLIENT_SECRET ?? ctxEnv.INFISICAL_CLIENT_SECRET,
|
||||
projectId: process.env.INFISICAL_PROJECT_ID ?? ctxEnv.INFISICAL_PROJECT_ID,
|
||||
runtimeEnv.INFISICAL_CLIENT_SECRET ?? ctxEnv.INFISICAL_CLIENT_SECRET,
|
||||
projectId: runtimeEnv.INFISICAL_PROJECT_ID ?? ctxEnv.INFISICAL_PROJECT_ID,
|
||||
environment:
|
||||
process.env.INFISICAL_ENVIRONMENT ?? ctxEnv.INFISICAL_ENVIRONMENT,
|
||||
runtimeEnv.INFISICAL_ENVIRONMENT ?? ctxEnv.INFISICAL_ENVIRONMENT,
|
||||
});
|
||||
|
||||
27
server/src/external/infisical/initInfisical.ts
vendored
27
server/src/external/infisical/initInfisical.ts
vendored
@@ -1,18 +1,19 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { InfisicalSDK } from "@infisical/sdk";
|
||||
import { loadLocalEnv } from "@/utils/envUtils.js";
|
||||
import { mask } from "@/utils/genUtils";
|
||||
/**
|
||||
* Initialize Infisical and load secrets into process.env
|
||||
* This allows all existing code using process.env to work seamlessly
|
||||
* Initialize Infisical and load secrets into runtimeEnv
|
||||
* This allows all existing code using runtimeEnv to work seamlessly
|
||||
*/
|
||||
export const initInfisical = async (params?: { secretPath?: string }) => {
|
||||
loadLocalEnv();
|
||||
|
||||
// Only initialize if credentials are provided
|
||||
const clientId = process.env.INFISICAL_CLIENT_ID;
|
||||
const clientSecret = process.env.INFISICAL_CLIENT_SECRET;
|
||||
const projectId = process.env.INFISICAL_PROJECT_ID;
|
||||
const environment = process.env.INFISICAL_ENVIRONMENT;
|
||||
const clientId = runtimeEnv.INFISICAL_CLIENT_ID;
|
||||
const clientSecret = runtimeEnv.INFISICAL_CLIENT_SECRET;
|
||||
const projectId = runtimeEnv.INFISICAL_PROJECT_ID;
|
||||
const environment = runtimeEnv.INFISICAL_ENVIRONMENT;
|
||||
|
||||
if (!clientId || !clientSecret || !projectId || !environment) {
|
||||
console.log("⚠️ Infisical credentials not found - skipping initialization");
|
||||
@@ -37,8 +38,8 @@ export const initInfisical = async (params?: { secretPath?: string }) => {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
// Load secrets into process.env
|
||||
// Note: Existing process.env variables take precedence (won't be overridden)
|
||||
// Load secrets into runtimeEnv
|
||||
// Note: Existing runtimeEnv variables take precedence (won't be overridden)
|
||||
let loadedCount = 0;
|
||||
|
||||
for (const secret of allSecrets.secrets) {
|
||||
@@ -48,22 +49,22 @@ export const initInfisical = async (params?: { secretPath?: string }) => {
|
||||
`Retrieving restricted secret: ${secret.secretKey}, Path: ${secret.secretPath}, value: ${mask(secret.secretValue, 3, 2)}`,
|
||||
);
|
||||
}
|
||||
if (!process.env[secret.secretKey]) {
|
||||
process.env[secret.secretKey] = secret.secretValue;
|
||||
if (!runtimeEnv[secret.secretKey]) {
|
||||
runtimeEnv[secret.secretKey] = secret.secretValue;
|
||||
loadedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const importSecrets of allSecrets?.imports ?? []) {
|
||||
for (const importSecret of importSecrets.secrets) {
|
||||
if (!process.env[importSecret.secretKey]) {
|
||||
process.env[importSecret.secretKey] = importSecret.secretValue;
|
||||
if (!runtimeEnv[importSecret.secretKey]) {
|
||||
runtimeEnv[importSecret.secretKey] = importSecret.secretValue;
|
||||
loadedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Infisical: loaded ${loadedCount} secrets into process.env`);
|
||||
console.log(`✅ Infisical: loaded ${loadedCount} secrets into runtimeEnv`);
|
||||
} catch (error) {
|
||||
console.error("❌ Failed to initialize Infisical:", error);
|
||||
throw error;
|
||||
|
||||
34
server/src/external/logtail/logtailUtils.ts
vendored
34
server/src/external/logtail/logtailUtils.ts
vendored
@@ -1,9 +1,8 @@
|
||||
import "dotenv/config";
|
||||
|
||||
import type pino from "pino";
|
||||
import { initLogger } from "@/utils/logging/initLogger";
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
|
||||
const pinoLogger = initLogger();
|
||||
const pinoLogger = initLogger({}, runtimeEnv);
|
||||
|
||||
const createLogMethod = (pinoMethod: any, logtailMethod?: any) => {
|
||||
function rewriteAppPath(str: string) {
|
||||
@@ -73,7 +72,10 @@ const createLogMethod = (pinoMethod: any, logtailMethod?: any) => {
|
||||
};
|
||||
};
|
||||
|
||||
const createLoggerStructure = (basePinoLogger: pino.Logger): Logger => ({
|
||||
const createLoggerStructure = (
|
||||
basePinoLogger: pino.Logger,
|
||||
env: Env,
|
||||
): Logger => ({
|
||||
debug: createLogMethod(basePinoLogger.debug.bind(basePinoLogger)),
|
||||
info: createLogMethod(basePinoLogger.info.bind(basePinoLogger)),
|
||||
warn: createLogMethod(basePinoLogger.warn.bind(basePinoLogger)),
|
||||
@@ -85,16 +87,20 @@ const createLoggerStructure = (basePinoLogger: pino.Logger): Logger => ({
|
||||
context: any;
|
||||
onlyProd?: boolean;
|
||||
}) => {
|
||||
if (onlyProd && process.env.NODE_ENV !== "production") {
|
||||
return createLoggerStructure(basePinoLogger);
|
||||
if (onlyProd && (env.NODE_ENV as string | undefined) !== "production") {
|
||||
return createLoggerStructure(basePinoLogger, env);
|
||||
}
|
||||
|
||||
const childPinoLogger = basePinoLogger.child(context);
|
||||
return createLoggerStructure(childPinoLogger);
|
||||
return createLoggerStructure(childPinoLogger, env);
|
||||
},
|
||||
});
|
||||
|
||||
export const createLogger = () => createLoggerStructure(pinoLogger);
|
||||
export const createLogger = (env: Env) =>
|
||||
createLoggerStructure(
|
||||
env === runtimeEnv ? pinoLogger : initLogger({}, env),
|
||||
env,
|
||||
);
|
||||
|
||||
/**
|
||||
* Lazy dual-output logger (stdout JSON + axiom). Used only by long-running
|
||||
@@ -102,12 +108,16 @@ export const createLogger = () => createLoggerStructure(pinoLogger);
|
||||
* our axiom store. Default `logger` / `createLogger` are unaffected.
|
||||
*/
|
||||
let dualPinoLogger: pino.Logger | null = null;
|
||||
export const createDualLogger = () => {
|
||||
if (!dualPinoLogger) dualPinoLogger = initLogger({ mode: "dual" });
|
||||
return createLoggerStructure(dualPinoLogger);
|
||||
export const createDualLogger = (env: Env) => {
|
||||
if (env !== runtimeEnv) {
|
||||
return createLoggerStructure(initLogger({ mode: "dual" }, env), env);
|
||||
}
|
||||
if (!dualPinoLogger)
|
||||
dualPinoLogger = initLogger({ mode: "dual" }, runtimeEnv);
|
||||
return createLoggerStructure(dualPinoLogger, runtimeEnv);
|
||||
};
|
||||
|
||||
export const logger = createLogger();
|
||||
export const logger = createLogger(runtimeEnv);
|
||||
export type Logger = {
|
||||
debug: (...args: any[]) => void;
|
||||
info: (...args: any[]) => void;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { onAwsEcs } from "@/external/aws/ecs/onAwsEcs.js";
|
||||
|
||||
/**
|
||||
@@ -20,10 +21,10 @@ import { onAwsEcs } from "@/external/aws/ecs/onAwsEcs.js";
|
||||
export const getReachableDragonflyUrl = (url: string): string => {
|
||||
if (onAwsEcs()) return url;
|
||||
|
||||
const privateUrl = process.env.CACHE_V2_DRAGONFLY_URL?.trim();
|
||||
const privateUrl = runtimeEnv.CACHE_V2_DRAGONFLY_URL?.trim();
|
||||
if (!privateUrl || url.trim() !== privateUrl) return url;
|
||||
|
||||
const publicUrl = process.env.CACHE_V2_DRAGONFLY_PUBLIC_URL?.trim();
|
||||
const publicUrl = runtimeEnv.CACHE_V2_DRAGONFLY_PUBLIC_URL?.trim();
|
||||
if (!publicUrl) return url;
|
||||
|
||||
return publicUrl;
|
||||
|
||||
7
server/src/external/redis/initRedisV2.ts
vendored
7
server/src/external/redis/initRedisV2.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { Redis } from "ioredis";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import type { RedisV2InstanceName } from "@/internal/misc/redisV2Cache/redisV2CacheSchemas.js";
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
supportsUpstashShebangForRedisV2,
|
||||
} from "./initUtils/redisV2Config.js";
|
||||
|
||||
const rawDragonflyUrl = process.env.CACHE_V2_DRAGONFLY_URL?.trim();
|
||||
const rawDragonflyUrl = runtimeEnv.CACHE_V2_DRAGONFLY_URL?.trim();
|
||||
const dragonflyUrl = rawDragonflyUrl
|
||||
? getReachableDragonflyUrl(rawDragonflyUrl)
|
||||
: undefined;
|
||||
@@ -27,8 +28,8 @@ export const redisV2: Redis = createRedisConnection({
|
||||
});
|
||||
|
||||
const alternateInstanceUrls: Partial<Record<RedisV2InstanceName, string>> = {
|
||||
upstash: process.env.CACHE_V2_UPSTASH_URL?.trim() || undefined,
|
||||
redis: process.env.CACHE_V2_REDIS_URL?.trim() || undefined,
|
||||
upstash: runtimeEnv.CACHE_V2_UPSTASH_URL?.trim() || undefined,
|
||||
redis: runtimeEnv.CACHE_V2_REDIS_URL?.trim() || undefined,
|
||||
dragonfly: dragonflyUrl,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { Redis } from "ioredis";
|
||||
import { instrumentRedis } from "../otel/instrumentRedis.js";
|
||||
import { cacheBackupUrl } from "./redisConfig.js";
|
||||
import { registerRedisCommands } from "./registerRedisCommands.js";
|
||||
|
||||
const REDIS_COMMAND_TIMEOUT_MS =
|
||||
process.env.NODE_ENV === "production" ? 10_000 : 60_000;
|
||||
runtimeEnv.NODE_ENV === "production" ? 10_000 : 60_000;
|
||||
|
||||
const formatRedisEndpoint = ({ cacheUrl }: { cacheUrl: string }) => {
|
||||
try {
|
||||
@@ -36,8 +37,8 @@ export const createRedisClient = ({
|
||||
|
||||
const instance = new Redis(cacheUrl, {
|
||||
tls:
|
||||
process.env.CACHE_CERT && !cacheBackupUrl
|
||||
? { ca: process.env.CACHE_CERT }
|
||||
runtimeEnv.CACHE_CERT && !cacheBackupUrl
|
||||
? { ca: runtimeEnv.CACHE_CERT }
|
||||
: undefined,
|
||||
family: 4,
|
||||
keepAlive: 10000,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { Redis } from "ioredis";
|
||||
import { createDisabledRedis, createRedisClient } from "./createRedisClient.js";
|
||||
import {
|
||||
@@ -8,12 +9,14 @@ import {
|
||||
primaryCacheUrl,
|
||||
} from "./redisConfig.js";
|
||||
|
||||
if (process.env.CACHE_BACKUP_URL?.trim()) {
|
||||
if (runtimeEnv.CACHE_BACKUP_URL?.trim()) {
|
||||
console.log(
|
||||
`[Redis] Using CACHE_BACKUP_URL for all regions (primary region: ${currentRegion})`,
|
||||
);
|
||||
} else if (!hasRedisConfig) {
|
||||
console.warn("[Redis] No Redis URL configured. Running in Postgres-only mode.");
|
||||
console.warn(
|
||||
"[Redis] No Redis URL configured. Running in Postgres-only mode.",
|
||||
);
|
||||
} else if (primaryCacheUrl && getCacheUrlForRegion({ region: currentRegion })) {
|
||||
console.log(`Using regional cache: ${currentRegion}`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Region constants
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
const REGION_US_EAST_2 = "us-east-2";
|
||||
const REGION_US_WEST_2 = "us-west-2";
|
||||
|
||||
@@ -6,9 +7,9 @@ const REGION_US_WEST_2 = "us-west-2";
|
||||
const ALL_REGIONS = [REGION_US_EAST_2, REGION_US_WEST_2] as const;
|
||||
|
||||
// Current region this instance is running in
|
||||
export const currentRegion = process.env.AWS_REGION || REGION_US_WEST_2;
|
||||
export const currentRegion = runtimeEnv.AWS_REGION || REGION_US_WEST_2;
|
||||
|
||||
export const cacheBackupUrl = process.env.CACHE_BACKUP_URL?.trim();
|
||||
export const cacheBackupUrl = runtimeEnv.CACHE_BACKUP_URL?.trim();
|
||||
|
||||
// Map of region to cache URL. When CACHE_BACKUP_URL is set, all regions use it
|
||||
// (failover / single backup endpoint).
|
||||
@@ -18,12 +19,12 @@ const regionToCacheUrl: Record<string, string | undefined> = cacheBackupUrl
|
||||
[REGION_US_WEST_2]: cacheBackupUrl,
|
||||
}
|
||||
: {
|
||||
[REGION_US_EAST_2]: process.env.CACHE_URL_US_EAST,
|
||||
[REGION_US_WEST_2]: process.env.CACHE_URL,
|
||||
[REGION_US_EAST_2]: runtimeEnv.CACHE_URL_US_EAST,
|
||||
[REGION_US_WEST_2]: runtimeEnv.CACHE_URL,
|
||||
};
|
||||
|
||||
export const primaryCacheUrl =
|
||||
regionToCacheUrl[currentRegion] || process.env.CACHE_URL || cacheBackupUrl;
|
||||
regionToCacheUrl[currentRegion] || runtimeEnv.CACHE_URL || cacheBackupUrl;
|
||||
|
||||
export const hasRedisConfig = Boolean(primaryCacheUrl);
|
||||
/** Get all regions that have configured cache URLs */
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { RedisV2InstanceName } from "@/internal/misc/redisV2Cache/redisV2CacheSchemas.js";
|
||||
|
||||
export const REDIS_V2_COMMAND_TIMEOUT_MS =
|
||||
process.env.NODE_ENV === "production" ? 1_000 : 10_000;
|
||||
runtimeEnv.NODE_ENV === "production" ? 1_000 : 10_000;
|
||||
|
||||
export const getRedisV2ConnectionConfig = ({
|
||||
cacheV2Url,
|
||||
|
||||
5
server/src/external/resend/loopsUtils.ts
vendored
5
server/src/external/resend/loopsUtils.ts
vendored
@@ -1,13 +1,14 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { User } from "better-auth";
|
||||
import { LoopsClient } from "loops";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
const createLoopsCli = () => {
|
||||
return new LoopsClient(process.env.LOOPS_API_KEY || "");
|
||||
return new LoopsClient(runtimeEnv.LOOPS_API_KEY || "");
|
||||
};
|
||||
|
||||
export const createLoopsContact = async (user: User) => {
|
||||
if (!process.env.LOOPS_API_KEY) return;
|
||||
if (!runtimeEnv.LOOPS_API_KEY) return;
|
||||
|
||||
try {
|
||||
const email = user.email;
|
||||
|
||||
3
server/src/external/resend/resendUtils.ts
vendored
3
server/src/external/resend/resendUtils.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { Resend } from "resend";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
@@ -11,7 +12,7 @@ interface ResendEmailProps {
|
||||
}
|
||||
|
||||
export const createResendCli = () => {
|
||||
return new Resend(process.env.RESEND_API_KEY);
|
||||
return new Resend(runtimeEnv.RESEND_API_KEY);
|
||||
};
|
||||
|
||||
export const sendTextEmail = async ({
|
||||
|
||||
3
server/src/external/resend/safeResend.ts
vendored
3
server/src/external/resend/safeResend.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
export function safeResend<T extends (...args: any[]) => any>({
|
||||
@@ -8,7 +9,7 @@ export function safeResend<T extends (...args: any[]) => any>({
|
||||
action: string;
|
||||
}): (...args: Parameters<T>) => Promise<ReturnType<T> | undefined> {
|
||||
return async (...args: Parameters<T>) => {
|
||||
if (!process.env.RESEND_API_KEY || !process.env.RESEND_DOMAIN) {
|
||||
if (!runtimeEnv.RESEND_API_KEY || !runtimeEnv.RESEND_DOMAIN) {
|
||||
logger.warn(
|
||||
`RESEND_API_KEY or RESEND_DOMAIN is not set, skipping ${action}`,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import type { initRevenuecatCli } from "./initRevenuecatCli.js";
|
||||
|
||||
@@ -8,9 +9,9 @@ type RcCli = ReturnType<typeof initRevenuecatCli>;
|
||||
* can reach a local tunnel); production uses BETTER_AUTH_URL.
|
||||
*/
|
||||
const getServerBaseUrl = (): string | undefined =>
|
||||
process.env.NODE_ENV !== "production"
|
||||
? process.env.NGROK_URL
|
||||
: process.env.BETTER_AUTH_URL;
|
||||
runtimeEnv.NODE_ENV !== "production"
|
||||
? runtimeEnv.NGROK_URL
|
||||
: runtimeEnv.BETTER_AUTH_URL;
|
||||
|
||||
export const getRevenuecatWebhookUrl = ({
|
||||
orgId,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
CodeChallengeMethod,
|
||||
generateCodeVerifier,
|
||||
@@ -48,8 +49,8 @@ export const findMissingRcScopes = (grantedScopes: string[]): string[] => {
|
||||
};
|
||||
|
||||
const getRcOAuthClient = () => {
|
||||
const clientId = process.env.REVENUECAT_OAUTH_CLIENT_ID;
|
||||
const clientSecret = process.env.REVENUECAT_OAUTH_CLIENT_SECRET;
|
||||
const clientId = runtimeEnv.REVENUECAT_OAUTH_CLIENT_ID;
|
||||
const clientSecret = runtimeEnv.REVENUECAT_OAUTH_CLIENT_SECRET;
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new Error("RevenueCat OAuth client credentials not configured");
|
||||
@@ -59,10 +60,10 @@ const getRcOAuthClient = () => {
|
||||
};
|
||||
|
||||
export const getRcOAuthRedirectUri = () => {
|
||||
let serverUrl = process.env.BETTER_AUTH_URL;
|
||||
let serverUrl = runtimeEnv.BETTER_AUTH_URL;
|
||||
|
||||
if (process.env.NGROK_URL) {
|
||||
serverUrl = process.env.NGROK_URL;
|
||||
if (runtimeEnv.NGROK_URL) {
|
||||
serverUrl = runtimeEnv.NGROK_URL;
|
||||
}
|
||||
|
||||
return `${(serverUrl ?? "").replace(/\/+$/, "")}/revenuecat/oauth_callback`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import * as Sentry from "@sentry/bun";
|
||||
import type { Context } from "hono";
|
||||
import { Stripe } from "stripe";
|
||||
@@ -122,7 +123,7 @@ export const handleStripeWebhookEvent = async (
|
||||
}
|
||||
|
||||
if (
|
||||
process.env.NODE_ENV === "development" &&
|
||||
runtimeEnv.NODE_ENV === "development" &&
|
||||
error instanceof Error &&
|
||||
error.message.includes("No stripe account linked to organization")
|
||||
) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { type AppEnv, ErrCode } from "@autumn/shared";
|
||||
import Stripe from "stripe";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
@@ -20,8 +21,7 @@ export const createWebhookEndpoint = async (
|
||||
) => {
|
||||
const stripe = new Stripe(apiKey);
|
||||
|
||||
const webhookBaseUrl =
|
||||
process.env.STRIPE_WEBHOOK_URL || process.env.SERVER_URL;
|
||||
const webhookBaseUrl = runtimeEnv.STRIPE_WEBHOOK_URL || runtimeEnv.SERVER_URL;
|
||||
|
||||
if (!webhookBaseUrl) {
|
||||
throw new RecaseError({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { getPrimaryRedis } from "@/external/redis/initRedis";
|
||||
import { tryRedisRead, tryRedisWrite } from "@/utils/cacheUtils/cacheUtils";
|
||||
|
||||
@@ -15,7 +16,7 @@ export const setStripeSubscriptionLock = async ({
|
||||
`sub:${stripeSubscriptionId}`,
|
||||
JSON.stringify({ lockedAtMs }),
|
||||
"EX",
|
||||
process.env.NODE_ENV === "production" ? 60 : 3,
|
||||
runtimeEnv.NODE_ENV === "production" ? 60 : 3,
|
||||
),
|
||||
primaryRedis,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createStripeCli } from "@/external/connect/createStripeCli";
|
||||
import { isStripeSubscriptionScheduleInLastPhase } from "@/external/stripe/subscriptionSchedules/utils/classifyStripeSubscriptionScheduleUtils";
|
||||
import { stripeSubscriptionScheduleToPhaseIndex } from "@/external/stripe/subscriptionSchedules/utils/convertStripeSubscriptionScheduleUtils";
|
||||
@@ -73,7 +74,7 @@ export const releaseScheduleIfLastPhase = async ({
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (runtimeEnv.NODE_ENV === "development") {
|
||||
logger.warn(
|
||||
`[handleSchedulePhaseChanges] failed to release schedule: ${error.message}`,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
type AppEnv,
|
||||
AuthType,
|
||||
@@ -54,8 +55,8 @@ export const stripeConnectSeederMiddleware = async (
|
||||
const signature = c.req.header("stripe-signature") || "";
|
||||
|
||||
const skipVerify =
|
||||
process.env.STRIPE_WEBHOOK_SKIP_VERIFY === "true" &&
|
||||
process.env.NODE_ENV !== "production";
|
||||
runtimeEnv.STRIPE_WEBHOOK_SKIP_VERIFY === "true" &&
|
||||
runtimeEnv.NODE_ENV !== "production";
|
||||
|
||||
let event: Stripe.Event;
|
||||
if (skipVerify) {
|
||||
@@ -78,7 +79,7 @@ export const stripeConnectSeederMiddleware = async (
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
if (runtimeEnv.NODE_ENV !== "development") {
|
||||
logger.warn(`Webhook verification error: ${message}`);
|
||||
}
|
||||
return c.json({ error: message }, 400);
|
||||
@@ -114,7 +115,7 @@ export const stripeConnectSeederMiddleware = async (
|
||||
return c.json({ error: "Failed to resolve org for Stripe webhook" }, 500);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
if (runtimeEnv.NODE_ENV !== "development") {
|
||||
logger.error(
|
||||
`Account ID ${accountId} not linked to any org, skipping Stripe webhook`,
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { tryCatch } from "@autumn/shared";
|
||||
import type { Context, Next } from "hono";
|
||||
import { redis } from "@/external/redis/initRedis";
|
||||
@@ -16,7 +17,7 @@ export const stripeIdempotencyMiddleware = async (
|
||||
c: Context<StripeWebhookHonoEnv>,
|
||||
next: Next,
|
||||
) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (runtimeEnv.NODE_ENV === "development") {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { type AppEnv, AuthType } from "@autumn/shared";
|
||||
import type { Context, Next } from "hono";
|
||||
import Stripe from "stripe";
|
||||
@@ -54,8 +55,8 @@ export const stripeLegacySeederMiddleware = async (
|
||||
const signature = c.req.header("stripe-signature") || "";
|
||||
|
||||
const skipVerify =
|
||||
process.env.STRIPE_WEBHOOK_SKIP_VERIFY === "true" &&
|
||||
process.env.NODE_ENV !== "production";
|
||||
runtimeEnv.STRIPE_WEBHOOK_SKIP_VERIFY === "true" &&
|
||||
runtimeEnv.NODE_ENV !== "production";
|
||||
|
||||
let event: Stripe.Event;
|
||||
if (skipVerify) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { isSyncableEvent, processStripeSyncEvent } from "@autumn/stripe-sync";
|
||||
import type { Context, Next } from "hono";
|
||||
import { isStripeSyncEnabled } from "@/internal/misc/stripeSync/stripeSyncStore.js";
|
||||
@@ -21,7 +22,7 @@ export const stripeSyncMiddleware = async (
|
||||
|
||||
if (!org || !stripeEvent) return;
|
||||
if (
|
||||
process.env.NODE_ENV === "production" &&
|
||||
runtimeEnv.NODE_ENV === "production" &&
|
||||
!isStripeSyncEnabled({ orgId: org.id, orgSlug: org.slug })
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
export const createSupabaseClient = () => {
|
||||
try {
|
||||
return createClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_KEY!,
|
||||
runtimeEnv.SUPABASE_URL!,
|
||||
runtimeEnv.SUPABASE_SERVICE_KEY!,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error creating Supabase client:", error);
|
||||
|
||||
3
server/src/external/svix/svixHelpers.ts
vendored
3
server/src/external/svix/svixHelpers.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { AppEnv, Organization } from "@autumn/shared";
|
||||
import * as Sentry from "@sentry/bun";
|
||||
import { getSentryTags } from "@/external/sentry/sentryUtils.js";
|
||||
@@ -53,7 +54,7 @@ export const sendSvixEvent = async ({
|
||||
idempotencyKey?: string;
|
||||
tags?: string[];
|
||||
}) => {
|
||||
if (!process.env.SVIX_API_KEY) return;
|
||||
if (!runtimeEnv.SVIX_API_KEY) return;
|
||||
|
||||
const { org, env } = ctx;
|
||||
|
||||
|
||||
5
server/src/external/svix/svixUtils.ts
vendored
5
server/src/external/svix/svixUtils.ts
vendored
@@ -1,9 +1,10 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
import { Svix } from "svix";
|
||||
import { logger } from "../logtail/logtailUtils.js";
|
||||
|
||||
export const createSvixCli = () => {
|
||||
return new Svix(process.env.SVIX_API_KEY as string);
|
||||
return new Svix(runtimeEnv.SVIX_API_KEY as string);
|
||||
};
|
||||
|
||||
export function safeSvix<T extends (...args: any[]) => any>({
|
||||
@@ -14,7 +15,7 @@ export function safeSvix<T extends (...args: any[]) => any>({
|
||||
action: string;
|
||||
}): (...args: Parameters<T>) => Promise<ReturnType<T> | undefined> {
|
||||
return async (...args: Parameters<T>) => {
|
||||
if (!process.env.SVIX_API_KEY) {
|
||||
if (!runtimeEnv.SVIX_API_KEY) {
|
||||
logger.warn(`SVIX_API_KEY is not set, skipping ${action}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { type ClickHouseClient, createClient } from "@clickhouse/client";
|
||||
|
||||
const TINYBIRD_CLICKHOUSE_URL = process.env.TINYBIRD_US_EAST_CLICKHOUSE_URL;
|
||||
const TINYBIRD_TOKEN = process.env.TINYBIRD_US_EAST_TOKEN;
|
||||
const TINYBIRD_CLICKHOUSE_URL = runtimeEnv.TINYBIRD_US_EAST_CLICKHOUSE_URL;
|
||||
const TINYBIRD_TOKEN = runtimeEnv.TINYBIRD_US_EAST_TOKEN;
|
||||
|
||||
if (TINYBIRD_CLICKHOUSE_URL && TINYBIRD_TOKEN) {
|
||||
console.log(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createTinybirdApi } from "@tinybirdco/sdk";
|
||||
|
||||
const TINYBIRD_SECONDARY_API_URL = process.env.TINYBIRD_API_URL;
|
||||
const TINYBIRD_SECONDARY_TOKEN = process.env.TINYBIRD_TOKEN;
|
||||
const TINYBIRD_SECONDARY_API_URL = runtimeEnv.TINYBIRD_API_URL;
|
||||
const TINYBIRD_SECONDARY_TOKEN = runtimeEnv.TINYBIRD_TOKEN;
|
||||
|
||||
/** Secondary Tinybird API client for dual-write safety net during region cutover.
|
||||
* Reads from the legacy TINYBIRD_API_URL / TINYBIRD_TOKEN env vars (europe-west2
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
defineDatasource,
|
||||
defineEndpoint,
|
||||
@@ -9,8 +10,8 @@ import {
|
||||
t,
|
||||
} from "@tinybirdco/sdk";
|
||||
|
||||
const TINYBIRD_US_EAST_API_URL = process.env.TINYBIRD_US_EAST_API_URL;
|
||||
const TINYBIRD_US_EAST_TOKEN = process.env.TINYBIRD_US_EAST_TOKEN;
|
||||
const TINYBIRD_US_EAST_API_URL = runtimeEnv.TINYBIRD_US_EAST_API_URL;
|
||||
const TINYBIRD_US_EAST_TOKEN = runtimeEnv.TINYBIRD_US_EAST_TOKEN;
|
||||
|
||||
const migrationTinybirdConfig =
|
||||
TINYBIRD_US_EAST_API_URL && TINYBIRD_US_EAST_TOKEN
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError } from "@autumn/shared";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
const TINYBIRD_API_URL = process.env.TINYBIRD_US_EAST_API_URL;
|
||||
const TINYBIRD_TOKEN = process.env.TINYBIRD_US_EAST_TOKEN;
|
||||
const TINYBIRD_API_URL = runtimeEnv.TINYBIRD_US_EAST_API_URL;
|
||||
const TINYBIRD_TOKEN = runtimeEnv.TINYBIRD_US_EAST_TOKEN;
|
||||
|
||||
export type TinybirdConfig = {
|
||||
baseUrl: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { AppEnv, type Organization } from "@autumn/shared";
|
||||
import { createRemoteJWKSet, jwtVerify } from "jose";
|
||||
import { JWTExpired, JWTInvalid } from "jose/errors";
|
||||
@@ -42,7 +43,7 @@ const synthesizeTestClaims = ({
|
||||
env: AppEnv;
|
||||
testOptions?: VercelOidcTestOptions;
|
||||
}): OidcClaims | null => {
|
||||
if (process.env.NODE_ENV === "production") return null;
|
||||
if (runtimeEnv.NODE_ENV === "production") return null;
|
||||
if (testOptions?.allowVercelTestOidc !== true) return null;
|
||||
if (!token.startsWith(TEST_OIDC_PREFIX)) return null;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
export type VercelSdkTestOptions = {
|
||||
mockVercelApi?: boolean;
|
||||
};
|
||||
@@ -8,9 +9,9 @@ export type VercelSdkTestOptions = {
|
||||
export const getVercelSdkServerURL = (
|
||||
testOptions?: VercelSdkTestOptions,
|
||||
): string | undefined => {
|
||||
if (process.env.NODE_ENV === "production") return undefined;
|
||||
if (runtimeEnv.NODE_ENV === "production") return undefined;
|
||||
if (testOptions?.mockVercelApi !== true) return undefined;
|
||||
const base = process.env.BETTER_AUTH_URL;
|
||||
const base = runtimeEnv.BETTER_AUTH_URL;
|
||||
if (!base) return undefined;
|
||||
return `${base.replace(/\/$/, "")}/__test/vercel/api`;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
ApiVersionClass,
|
||||
AppEnv,
|
||||
@@ -79,7 +80,7 @@ export const baseMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
entity_id: entityId,
|
||||
user_agent: c.req.header("user-agent"),
|
||||
ip_address: c.req.header("x-forwarded-for"),
|
||||
region: process.env.AWS_REGION,
|
||||
region: runtimeEnv.AWS_REGION,
|
||||
query: c.req.query(),
|
||||
body: redactSensitiveRequestBody({ body }),
|
||||
|
||||
@@ -129,7 +130,7 @@ export const baseMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
useReplica: c.req.header("x-use-replica") === "true",
|
||||
mockVercelApi: c.req.header("x-mock-vercel-api") === "true",
|
||||
allowVercelTestOidc:
|
||||
process.env.NODE_ENV !== "production" &&
|
||||
runtimeEnv.NODE_ENV !== "production" &&
|
||||
c.req.header("x-allow-vercel-test-oidc") === "true",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError } from "@autumn/shared";
|
||||
import type { Context } from "hono";
|
||||
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
@@ -47,7 +48,7 @@ const ROUTE_SPECIFIC_RULES: Array<{
|
||||
match: (err: Error) =>
|
||||
err.message.includes(
|
||||
"STRIPE_WEBHOOK_SECRET env variable is not set (live)",
|
||||
) && process.env.NODE_ENV === "development",
|
||||
) && runtimeEnv.NODE_ENV === "development",
|
||||
statusCode: 500,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { Context, Env, Next } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import {
|
||||
@@ -24,9 +25,9 @@ export const rateLimitMiddleware = async (c: Context<HonoEnv>, next: Next) => {
|
||||
|
||||
if (
|
||||
rateLimitType === RateLimitType.Attach &&
|
||||
(process.env.NODE_ENV === "development" ||
|
||||
process.env.NODE_ENV === "test") &&
|
||||
ctx.org?.id === process.env.TESTS_ORG_ID
|
||||
(runtimeEnv.NODE_ENV === "development" ||
|
||||
runtimeEnv.NODE_ENV === "test") &&
|
||||
ctx.org?.id === runtimeEnv.TESTS_ORG_ID
|
||||
) {
|
||||
return await next();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import chalk from "chalk";
|
||||
import type { Context } from "hono";
|
||||
import type { AutumnContext, HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
@@ -14,7 +15,7 @@ const HIGH_VOLUME_SUCCESS_ROUTES = new Set<string>([
|
||||
]);
|
||||
|
||||
const SUCCESS_REQUEST_LOG_SAMPLE_RATE = Number.parseFloat(
|
||||
process.env.AXIOM_SUCCESS_REQUEST_LOG_SAMPLE_RATE ?? "0",
|
||||
runtimeEnv.AXIOM_SUCCESS_REQUEST_LOG_SAMPLE_RATE ?? "0",
|
||||
);
|
||||
|
||||
const shouldSampleSuccessLog = () =>
|
||||
@@ -80,7 +81,7 @@ export const logRequestResult = async ({
|
||||
|
||||
if (
|
||||
Object.keys(ctx.extraLogs).length > 0 &&
|
||||
process.env.NODE_ENV === "development"
|
||||
runtimeEnv.NODE_ENV === "development"
|
||||
) {
|
||||
const maskedLogs = maskExtraLogs(ctx.extraLogs);
|
||||
ctx.logger.debug(`EXTRA LOGS: ${JSON.stringify(maskedLogs, null, 2)}`);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { AuthType } from "@autumn/shared";
|
||||
import type { Context, Next } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
@@ -49,7 +50,7 @@ export const responseFilterMiddleware = async (
|
||||
const ctx = c.get("ctx");
|
||||
if (ctx?.authType === AuthType.Dashboard) return;
|
||||
const isNonProd =
|
||||
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
|
||||
runtimeEnv.NODE_ENV === "development" || runtimeEnv.NODE_ENV === "test";
|
||||
if (isNonProd && ctx?.testOptions?.keepInternalFields === true) return;
|
||||
|
||||
// Only process JSON responses
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { context, trace } from "@opentelemetry/api";
|
||||
import type { MiddlewareHandler } from "hono";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
@@ -28,7 +29,7 @@ export const traceEnrichMiddleware: MiddlewareHandler = async (c, next) => {
|
||||
user_id: ctx.userId || undefined,
|
||||
auth_type: ctx.authType,
|
||||
api_version: ctx.apiVersion?.semver,
|
||||
region: process.env.AWS_REGION,
|
||||
region: runtimeEnv.AWS_REGION,
|
||||
full_subject_rollout_enabled: ctx.org
|
||||
? isFullSubjectRolloutEnabled({ ctx })
|
||||
: undefined,
|
||||
|
||||
@@ -92,6 +92,7 @@ export type RequestContext = {
|
||||
export type AutumnContext = RequestContext;
|
||||
|
||||
export type HonoEnv = {
|
||||
Bindings: Env;
|
||||
Variables: {
|
||||
ctx: AutumnContext;
|
||||
validated: boolean;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { timingSafeEqual } from "node:crypto";
|
||||
import type { Context } from "hono";
|
||||
import { clientCritical } from "@/db/initDrizzle.js";
|
||||
@@ -7,7 +8,7 @@ import { getRedisV2Availability } from "@/external/redis/initUtils/redisV2Availa
|
||||
import type { HonoEnv } from "./HonoEnv.js";
|
||||
|
||||
const POSTGRES_TIMEOUT_MS = 1_000;
|
||||
const READY_CHECK_TOKEN = process.env.READY_CHECK_TOKEN?.trim();
|
||||
const READY_CHECK_TOKEN = runtimeEnv.READY_CHECK_TOKEN?.trim();
|
||||
|
||||
const checkPostgresReady = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { createHonoApp } from "./initHono.js";
|
||||
import { setRuntimeEnv } from "@/utils/envUtils.js";
|
||||
import type { ExecutionContext } from "hono";
|
||||
import type { createHonoApp } from "./initHono.js";
|
||||
|
||||
const app = createHonoApp();
|
||||
let app: ReturnType<typeof createHonoApp> | null = null;
|
||||
|
||||
export default {
|
||||
fetch(request: Request, env: unknown, executionCtx: ExecutionContext) {
|
||||
async fetch(request: Request, env: Env, executionCtx: ExecutionContext) {
|
||||
setRuntimeEnv(env);
|
||||
const { createHonoApp } = await import("./initHono.js");
|
||||
app ??= createHonoApp(env);
|
||||
return app.fetch(request, env, executionCtx);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Sentry + OpenTelemetry must be imported before any application code
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
await import("./sentry.js");
|
||||
|
||||
import cluster from "node:cluster";
|
||||
@@ -55,7 +56,7 @@ let shuttingDown = false;
|
||||
const init = async ({ startupStartedAt }: { startupStartedAt: number }) => {
|
||||
logger.info(getRedactedDatabaseUrls(), "DB URLs");
|
||||
|
||||
const app = createHonoApp();
|
||||
const app = createHonoApp(runtimeEnv as unknown as Env);
|
||||
|
||||
initPgHealthMonitor({ client: clientCritical });
|
||||
startPgPoolMonitor();
|
||||
@@ -72,8 +73,8 @@ const init = async ({ startupStartedAt }: { startupStartedAt: number }) => {
|
||||
startRedisMonitor();
|
||||
startRedisV2Monitor();
|
||||
|
||||
const PORT = process.env.SERVER_PORT
|
||||
? Number.parseInt(process.env.SERVER_PORT)
|
||||
const PORT = runtimeEnv.SERVER_PORT
|
||||
? Number.parseInt(runtimeEnv.SERVER_PORT)
|
||||
: 8080;
|
||||
|
||||
const requestListener = getRequestListener(app.fetch);
|
||||
@@ -94,7 +95,7 @@ const init = async ({ startupStartedAt }: { startupStartedAt: number }) => {
|
||||
});
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (runtimeEnv.NODE_ENV === "development") {
|
||||
registerFatalErrorHandlers();
|
||||
await init({ startupStartedAt: Date.now() });
|
||||
registerShutdownHandlers();
|
||||
|
||||
@@ -15,8 +15,8 @@ import { handleReadyCheck } from "./honoUtils/handleReadyCheck.js";
|
||||
import { handleListAuthOrganizations } from "./internal/auth/handleListAuthOrganizations.js";
|
||||
import { oauthRouter } from "./internal/auth/oauth/oauthRouter.js";
|
||||
import { cliRouter } from "./internal/dev/cli/cliRouter.js";
|
||||
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js";
|
||||
import { handleRevenueCatOAuthCallback } from "./internal/orgs/handlers/revenueCatHandlers/handleRevenueCatOAuthCallback.js";
|
||||
import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js";
|
||||
import { apiRouter } from "./routers/apiRouter.js";
|
||||
import { createChatProxyRouter } from "./routers/chatProxyRouter.js";
|
||||
import { internalRouter } from "./routers/internalRouter.js";
|
||||
@@ -48,7 +48,7 @@ const ALLOWED_HEADERS = [
|
||||
"User-Agent", // Required for better-auth v1.4.0+ compatibility with Safari/Zen browser
|
||||
];
|
||||
|
||||
export const createHonoApp = () => {
|
||||
export const createHonoApp = (env?: Env) => {
|
||||
const app = new Hono<HonoEnv>();
|
||||
|
||||
app.route("", createChatProxyRouter());
|
||||
@@ -100,7 +100,7 @@ export const createHonoApp = () => {
|
||||
// Add Render region identifier header for load balancer verification
|
||||
app.use("*", async (c, next) => {
|
||||
await next();
|
||||
c.header("x-region", process.env.AWS_REGION);
|
||||
c.header("x-region", c.env.AWS_REGION);
|
||||
});
|
||||
|
||||
// Webhook routes
|
||||
@@ -111,7 +111,7 @@ export const createHonoApp = () => {
|
||||
|
||||
// Vercel SDK test mock — mounted in dev/test, used only when
|
||||
// `ctx.testOptions.mockVercelApi` points the SDK at this route.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if ((env?.NODE_ENV as string | undefined) !== "production") {
|
||||
app.route("/__test/vercel/api", vercelTestApiRouter);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import "dotenv/config";
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { DiagConsoleLogger, DiagLogLevel, diag } from "@opentelemetry/api";
|
||||
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||
@@ -14,14 +14,14 @@ diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.WARN);
|
||||
|
||||
let sdk: NodeSDK | null = null;
|
||||
|
||||
if (process.env.AXIOM_TOKEN) {
|
||||
if (runtimeEnv.AXIOM_TOKEN) {
|
||||
// NodeSDK reads OTEL_SERVICE_NAME to set the service resource attribute
|
||||
process.env.OTEL_SERVICE_NAME = "autumn-server";
|
||||
runtimeEnv.OTEL_SERVICE_NAME = "autumn-server";
|
||||
|
||||
const traceExporter = new OTLPTraceExporter({
|
||||
url: "https://api.axiom.co/v1/traces",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
|
||||
Authorization: `Bearer ${runtimeEnv.AXIOM_TOKEN}`,
|
||||
"X-Axiom-Dataset": "otel",
|
||||
},
|
||||
});
|
||||
@@ -30,18 +30,18 @@ if (process.env.AXIOM_TOKEN) {
|
||||
// auto-add a BatchSpanProcessor for `traceExporter` when `spanProcessors`
|
||||
// is set. We must wire the exporter processor explicitly.
|
||||
// Dev: short 1s flush for fast feedback. Prod: default 5s for throughput.
|
||||
const isDev = process.env.NODE_ENV !== "production";
|
||||
const isDev = runtimeEnv.NODE_ENV !== "production";
|
||||
const exportProcessor = new BatchSpanProcessor(traceExporter, {
|
||||
scheduledDelayMillis: isDev ? 1000 : 5000,
|
||||
});
|
||||
const filteredExportProcessor = new FilteringSpanProcessor(exportProcessor);
|
||||
const metricReader = process.env.AXIOM_METRICS_DATASET
|
||||
const metricReader = runtimeEnv.AXIOM_METRICS_DATASET
|
||||
? new PeriodicExportingMetricReader({
|
||||
exporter: new OTLPMetricExporter({
|
||||
url: "https://api.axiom.co/v1/metrics",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
|
||||
"x-axiom-metrics-dataset": process.env.AXIOM_METRICS_DATASET,
|
||||
Authorization: `Bearer ${runtimeEnv.AXIOM_TOKEN}`,
|
||||
"x-axiom-metrics-dataset": runtimeEnv.AXIOM_METRICS_DATASET,
|
||||
},
|
||||
}),
|
||||
exportIntervalMillis: 60_000,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import crypto, { randomUUID } from "node:crypto";
|
||||
import { stripOAuthTokenPrefix } from "@autumn/auth";
|
||||
import {
|
||||
@@ -82,7 +83,7 @@ const getOrgSummary = async ({
|
||||
const decryptChatCredentialToken = ({ token }: { token: string }) => {
|
||||
const key = crypto
|
||||
.createHash("sha256")
|
||||
.update(process.env.ENCRYPTION_PASSWORD ?? "")
|
||||
.update(runtimeEnv.ENCRYPTION_PASSWORD ?? "")
|
||||
.digest();
|
||||
const buffer = Buffer.from(token, "base64");
|
||||
if (buffer[0] !== 1) throw new Error("Unsupported encrypted payload");
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError, Scopes } from "@autumn/shared";
|
||||
import { registerMcpOAuthClient } from "@/internal/auth/actions/index.js";
|
||||
import { createRoute } from "../../honoMiddlewares/routeHandler";
|
||||
|
||||
const getClientUrl = () =>
|
||||
(process.env.CLIENT_URL || "http://localhost:3000").replace(/\/+$/, "");
|
||||
(runtimeEnv.CLIENT_URL || "http://localhost:3000").replace(/\/+$/, "");
|
||||
|
||||
const getSlackMcpRedirectUris = () => {
|
||||
const clientUrl = getClientUrl();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, type EventInsert, events, RecaseError } from "@autumn/shared";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
@@ -14,7 +15,7 @@ export class EventService {
|
||||
event: EventInsert | EventInsert[];
|
||||
logger?: Logger;
|
||||
}) {
|
||||
if (process.env.NODE_ENV !== "development") return;
|
||||
if (runtimeEnv.NODE_ENV !== "development") return;
|
||||
try {
|
||||
const results = await db
|
||||
.insert(events)
|
||||
@@ -48,7 +49,7 @@ export class EventService {
|
||||
env: string;
|
||||
limit?: number;
|
||||
}) {
|
||||
if (process.env.NODE_ENV === "production") return [];
|
||||
if (runtimeEnv.NODE_ENV === "production") return [];
|
||||
const results = await db
|
||||
.select({
|
||||
id: events.id,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||
import { oauthClientRepo } from "../repos/index.js";
|
||||
|
||||
@@ -5,7 +6,7 @@ const ATMN_OAUTH_CLIENT_NAMES = new Set(["atmn", "autumn cli"]);
|
||||
|
||||
const configuredAtmnClientIds = () =>
|
||||
new Set(
|
||||
(process.env.ATMN_OAUTH_CLIENT_IDS ?? "")
|
||||
(runtimeEnv.ATMN_OAUTH_CLIENT_IDS ?? "")
|
||||
.split(",")
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { MCP_CLIENT_KIND } from "@autumn/auth/oauth";
|
||||
import type { Context } from "hono";
|
||||
import { type DrizzleCli, db } from "@/db/initDrizzle.js";
|
||||
import { auth } from "@/utils/auth.js";
|
||||
import { oauthClientRepo } from "../repos/index.js";
|
||||
|
||||
const INTERNAL_MCP_CLIENT_ID = process.env.INTERNAL_MCP_OAUTH_CLIENT_ID;
|
||||
const INTERNAL_MCP_CLIENT_ID = runtimeEnv.INTERNAL_MCP_OAUTH_CLIENT_ID;
|
||||
const INTERNAL_MCP_CLIENT_NAME = "Autumn internal-mcp";
|
||||
const INTERNAL_MCP_CLIENT_NAME_NORMALIZED =
|
||||
INTERNAL_MCP_CLIENT_NAME.toLowerCase();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { stripOAuthTokenPrefix } from "@autumn/auth";
|
||||
import {
|
||||
AppEnv,
|
||||
@@ -19,7 +20,7 @@ import { isAtmnOAuthClientId } from "./atmnOAuthClients.js";
|
||||
import { rotateOAuthConsentApiKey } from "./oauthConsentApiKey.js";
|
||||
|
||||
const getOAuthIssuer = () =>
|
||||
`${process.env.BETTER_AUTH_URL?.replace(/\/$/, "") ?? ""}/api/auth`;
|
||||
`${runtimeEnv.BETTER_AUTH_URL?.replace(/\/$/, "") ?? ""}/api/auth`;
|
||||
|
||||
const verifyResourceAccessToken = async ({
|
||||
accessToken,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
oauthProviderAuthServerMetadata,
|
||||
oauthProviderOpenIdConfigMetadata,
|
||||
@@ -22,7 +23,7 @@ const getClientLookupRateLimitKey = (c: Context<HonoEnv>) =>
|
||||
|
||||
const oauthClientLookupLimiter = rateLimiter<HonoEnv>({
|
||||
windowMs: 60 * 1000,
|
||||
limit: process.env.NODE_ENV === "development" ? 1000 : 60,
|
||||
limit: runtimeEnv.NODE_ENV === "development" ? 1000 : 60,
|
||||
standardHeaders: "draft-6",
|
||||
keyGenerator: getClientLookupRateLimitKey,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError, type TrackParams } from "@autumn/shared";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { queueTrack } from "./utils/queueTrack.js";
|
||||
@@ -12,7 +13,7 @@ export const runAsyncTrack = async ({
|
||||
ctx: AutumnContext;
|
||||
body: TrackParams;
|
||||
}): Promise<void> => {
|
||||
const queueUrl = process.env.TRACK_ASYNC_SQS_QUEUE_URL;
|
||||
const queueUrl = runtimeEnv.TRACK_ASYNC_SQS_QUEUE_URL;
|
||||
if (!queueUrl) {
|
||||
ctx.logger.error(
|
||||
"[track] async=true requested but TRACK_ASYNC_SQS_QUEUE_URL is unset",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { type BatchTrackParams, ErrCode, RecaseError } from "@autumn/shared";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
@@ -15,7 +16,7 @@ export const runBatchTrack = async ({
|
||||
ctx: AutumnContext;
|
||||
body: BatchTrackParams;
|
||||
}): Promise<void> => {
|
||||
const queueUrl = process.env.TRACK_ASYNC_SQS_QUEUE_URL;
|
||||
const queueUrl = runtimeEnv.TRACK_ASYNC_SQS_QUEUE_URL;
|
||||
if (!queueUrl) {
|
||||
ctx.logger.error(
|
||||
"[track] batch track requested but TRACK_ASYNC_SQS_QUEUE_URL is unset",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { TrackParams } from "@autumn/shared";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
@@ -17,7 +18,7 @@ export const queueTrack = async ({
|
||||
messageDeduplicationId?: string;
|
||||
}) => {
|
||||
try {
|
||||
const resolvedQueueUrl = queueUrl ?? process.env.TRACK_SQS_QUEUE_URL;
|
||||
const resolvedQueueUrl = queueUrl ?? runtimeEnv.TRACK_SQS_QUEUE_URL;
|
||||
if (!resolvedQueueUrl) {
|
||||
ctx.logger.warn(
|
||||
"[track] Redis unavailable and TRACK_SQS_QUEUE_URL is unset; falling back to synchronous track",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
type FullCusEntWithFullCusProduct,
|
||||
type FullSubject,
|
||||
@@ -232,7 +233,7 @@ export const executeRedisDeductionV2 = async ({
|
||||
}
|
||||
: null,
|
||||
unwind_value: unwindValue ?? null,
|
||||
debug: process.env.NODE_ENV !== "production",
|
||||
debug: runtimeEnv.NODE_ENV !== "production",
|
||||
};
|
||||
|
||||
const targetRedis = redisInstance ?? ctx.redisV2;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { AppEnv } from "@autumn/shared";
|
||||
import { JobName } from "@/queue/JobName.js";
|
||||
|
||||
export const REFRESH_ENTITY_AGGREGATE_DEDUP_BUCKET_MS =
|
||||
process.env.NODE_ENV === "development" ? 1000 : 5000;
|
||||
runtimeEnv.NODE_ENV === "development" ? 1000 : 5000;
|
||||
|
||||
/**
|
||||
* Buffer added after the bucket boundary so the trailing enqueue fires *after*
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { type AttachResponseV1, AttachResponseV1Schema, Scopes } from "@autumn/shared";
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
type AttachResponseV1,
|
||||
AttachResponseV1Schema,
|
||||
Scopes,
|
||||
} from "@autumn/shared";
|
||||
import { AttachBodyV0Schema } from "../../../../../shared/api/billing/attach/prevVersions/attachBodyV0";
|
||||
import {
|
||||
AffectedResource,
|
||||
@@ -21,7 +26,7 @@ export const handleAttach = createRoute({
|
||||
resource: AffectedResource.Attach,
|
||||
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 60000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
ApiVersion,
|
||||
@@ -19,7 +20,7 @@ export const handleAttachV2 = createRoute({
|
||||
},
|
||||
resource: AffectedResource.Attach,
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
CreateScheduleParamsV0Schema,
|
||||
type CreateScheduleResponse,
|
||||
Scopes,
|
||||
CreateScheduleParamsV0Schema,
|
||||
type CreateScheduleResponse,
|
||||
Scopes,
|
||||
} from "@autumn/shared";
|
||||
import { billingActions } from "@/internal/billing/v2/actions";
|
||||
import { buildBillingLockKey } from "@/internal/billing/v2/utils/billingLock/buildBillingLockKey";
|
||||
@@ -13,7 +14,7 @@ export const handleCreateSchedule = createRoute({
|
||||
body: CreateScheduleParamsV0Schema,
|
||||
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
InternalError,
|
||||
@@ -14,7 +15,7 @@ export const handleMultiAttach = createRoute({
|
||||
body: MultiAttachParamsV0Schema,
|
||||
resource: AffectedResource.MultiAttach,
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
ApiVersion,
|
||||
@@ -19,7 +20,7 @@ export const handleUpdateSubscription = createRoute({
|
||||
},
|
||||
resource: AffectedResource.ApiSubscriptionUpdate,
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { BillingContext, StripeSubscriptionAction } from "@autumn/shared";
|
||||
import { AppEnv, ErrCode, RecaseError } from "@autumn/shared";
|
||||
import { stripeSubscriptionToApplication } from "@/external/stripe/subscriptions/utils/convertStripeSubscription";
|
||||
@@ -6,8 +7,8 @@ import { isStripeConnected } from "@/internal/orgs/orgUtils";
|
||||
|
||||
const expectedStripeApplicationId = ({ ctx }: { ctx: AutumnContext }) =>
|
||||
ctx.env === AppEnv.Live
|
||||
? process.env.STRIPE_LIVE_CLIENT_ID
|
||||
: process.env.STRIPE_SANDBOX_CLIENT_ID;
|
||||
? runtimeEnv.STRIPE_LIVE_CLIENT_ID
|
||||
: runtimeEnv.STRIPE_SANDBOX_CLIENT_ID;
|
||||
|
||||
const shouldValidateStripeApplicationOwnership = ({
|
||||
ctx,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError } from "@autumn/shared";
|
||||
|
||||
export const slackProvider = "slack" as const;
|
||||
@@ -31,7 +32,7 @@ export const getMissingSlackScopes = (scopes: string[]) => {
|
||||
};
|
||||
|
||||
export const getRequiredChatEnv = (key: string) => {
|
||||
const value = process.env[key];
|
||||
const value = runtimeEnv[key];
|
||||
if (value) return value;
|
||||
|
||||
throw new RecaseError({
|
||||
@@ -42,20 +43,20 @@ export const getRequiredChatEnv = (key: string) => {
|
||||
};
|
||||
|
||||
export const getChatStateSecret = () =>
|
||||
process.env.CHAT_STATE_SECRET ??
|
||||
process.env.SLACK_STATE_SECRET ??
|
||||
process.env.BETTER_AUTH_SECRET ??
|
||||
runtimeEnv.CHAT_STATE_SECRET ??
|
||||
runtimeEnv.SLACK_STATE_SECRET ??
|
||||
runtimeEnv.BETTER_AUTH_SECRET ??
|
||||
getRequiredChatEnv("ENCRYPTION_PASSWORD");
|
||||
|
||||
export const createSlackInstallUrl = (state: string) => {
|
||||
const scope = process.env.SLACK_BOT_SCOPES ?? defaultSlackScopes.join(",");
|
||||
const scope = runtimeEnv.SLACK_BOT_SCOPES ?? defaultSlackScopes.join(",");
|
||||
const params = new URLSearchParams({
|
||||
client_id: getRequiredChatEnv("SLACK_CLIENT_ID"),
|
||||
scope,
|
||||
state,
|
||||
});
|
||||
if (process.env.SLACK_REDIRECT_URI) {
|
||||
params.set("redirect_uri", process.env.SLACK_REDIRECT_URI);
|
||||
if (runtimeEnv.SLACK_REDIRECT_URI) {
|
||||
params.set("redirect_uri", runtimeEnv.SLACK_REDIRECT_URI);
|
||||
}
|
||||
return `https://slack.com/oauth/v2/authorize?${params}`;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
AffectedResource,
|
||||
type Checkout,
|
||||
@@ -24,7 +25,7 @@ export const handleConfirmCheckout = createRoute({
|
||||
resource: AffectedResource.Attach,
|
||||
body: ConfirmCheckoutParamsSchema,
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { UpdateSubscriptionV1Params } from "@autumn/shared";
|
||||
import { Scopes } from "@autumn/shared";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler";
|
||||
@@ -16,7 +17,7 @@ import { logAutumnBillingPlan } from "@/internal/billing/v2/utils/logs/logAutumn
|
||||
export const handleCancelV2 = createRoute({
|
||||
scopes: [Scopes.Billing.Write],
|
||||
lock:
|
||||
process.env.NODE_ENV !== "development"
|
||||
runtimeEnv.NODE_ENV !== "development"
|
||||
? {
|
||||
ttlMs: 120000,
|
||||
errorMessage:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import type { AppEnv } from "@autumn/shared";
|
||||
import type { Redis } from "ioredis";
|
||||
import {
|
||||
@@ -18,7 +19,7 @@ type CustomerToDelete = {
|
||||
customerId: string;
|
||||
};
|
||||
|
||||
const isProductionNode = process.env.NODE_ENV === "production";
|
||||
const isProductionNode = runtimeEnv.NODE_ENV === "production";
|
||||
|
||||
/**
|
||||
* Per org: all keys share `{orgId}` so Redis Cluster stays in one slot per pipeline.
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export const FROM_AUTUMN = `Autumn <hey@${process.env.RESEND_DOMAIN}>`;
|
||||
export const FROM_AYUSH = `Ayush <ayush@${process.env.RESEND_DOMAIN}>`;
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
export const FROM_AUTUMN = `Autumn <hey@${runtimeEnv.RESEND_DOMAIN}>`;
|
||||
export const FROM_AYUSH = `Ayush <ayush@${runtimeEnv.RESEND_DOMAIN}>`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { sendTextEmail } from "@/external/resend/resendUtils.js";
|
||||
import { safeResend } from "@/external/resend/safeResend.js";
|
||||
@@ -8,7 +9,7 @@ const getInvitationEmailBody = ({ orgName }: { orgName: string }) => {
|
||||
|
||||
Click the link below to create an account / sign in to Autumn and accept the invitation.
|
||||
|
||||
${process.env.CLIENT_URL}/sign-in
|
||||
${runtimeEnv.CLIENT_URL}/sign-in
|
||||
`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { logger } from "@/external/logtail/logtailUtils.js";
|
||||
import { createResendCli } from "@/external/resend/resendUtils.js";
|
||||
import { FROM_AUTUMN } from "./constants.js";
|
||||
import OTPEmail from "./OTPEmail.js";
|
||||
|
||||
const sendOTPEmail = async ({ email, otp }: { email: string; otp: string }) => {
|
||||
if (!process.env.RESEND_API_KEY || !process.env.RESEND_DOMAIN) {
|
||||
if (!runtimeEnv.RESEND_API_KEY || !runtimeEnv.RESEND_DOMAIN) {
|
||||
logger.warn(`RESEND NOT SET UP, SIGN IN OTP: ${otp}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import {
|
||||
type ApiInvoiceV1,
|
||||
type Customer,
|
||||
@@ -43,7 +44,7 @@ export const processInvoice = ({
|
||||
currency: invoice.currency,
|
||||
created_at: invoice.created_at,
|
||||
hosted_invoice_url: isStripe
|
||||
? `${process.env.BETTER_AUTH_URL}/invoices/hosted_invoice_url/${invoice.id}`
|
||||
? `${runtimeEnv.BETTER_AUTH_URL}/invoices/hosted_invoice_url/${invoice.id}`
|
||||
: null,
|
||||
// hosted_invoice_url: invoice.hosted_invoice_url,
|
||||
// items: withItems
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
import {
|
||||
type Customer,
|
||||
type FullProduct,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, RecaseError, Scopes } from "@autumn/shared";
|
||||
import { auth } from "@trigger.dev/sdk/v3";
|
||||
import { z } from "zod/v4";
|
||||
@@ -72,7 +73,7 @@ export const handleRunMigration = createRoute({
|
||||
});
|
||||
}
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const isDev = runtimeEnv.NODE_ENV === "development";
|
||||
const { migrationRunId, triggerRunId } = await withMigrationRunClaim({
|
||||
ctx,
|
||||
migration,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import chalk from "chalk";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { addExtrasToLogs } from "@/utils/logging/addContextToLogs.js";
|
||||
@@ -62,7 +63,7 @@ export const logMigrateCustomerResult = ({
|
||||
|
||||
if (
|
||||
Object.keys(ctx.extraLogs).length > 0 &&
|
||||
process.env.NODE_ENV === "development"
|
||||
runtimeEnv.NODE_ENV === "development"
|
||||
) {
|
||||
const maskedLogs = maskExtraLogs(ctx.extraLogs);
|
||||
ctx.logger.debug(`EXTRA LOGS: ${JSON.stringify(maskedLogs, null, 2)}`);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ErrCode, ms } from "@autumn/shared";
|
||||
import type { S3Client } from "@aws-sdk/client-s3";
|
||||
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||
@@ -26,7 +27,7 @@ export const createEdgeConfigStore = <T>({
|
||||
s3Key,
|
||||
schema,
|
||||
defaultValue,
|
||||
pollIntervalMs = process.env.NODE_ENV === "development"
|
||||
pollIntervalMs = runtimeEnv.NODE_ENV === "development"
|
||||
? ms.seconds(1)
|
||||
: ms.seconds(10),
|
||||
s3Client: injectedS3Client,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { z } from "zod/v4";
|
||||
import { Scopes } from "@autumn/shared";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
@@ -16,7 +17,7 @@ export const handleSubmitFeedback = createRoute({
|
||||
const userEmail = ctx.user?.email ?? "Unknown user";
|
||||
const orgSlug = ctx.org?.slug ?? "Unknown org";
|
||||
|
||||
const webhookUrl = process.env.DISCORD_FEEDBACK_WEBHOOK;
|
||||
const webhookUrl = runtimeEnv.DISCORD_FEEDBACK_WEBHOOK;
|
||||
if (!webhookUrl) {
|
||||
console.warn("DISCORD_FEEDBACK_WEBHOOK not configured");
|
||||
return c.json({ success: true });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
import { type AgentPricingConfig, InternalError } from "@autumn/shared";
|
||||
import { withTracing } from "@posthog/ai";
|
||||
@@ -12,12 +13,12 @@ import { OrganisationConfigurationSchema } from "./pricingAgentSchemas.js";
|
||||
// PostHog client singleton
|
||||
let phClient: PostHog | null = null;
|
||||
const getPostHogClient = (): PostHog | null => {
|
||||
if (!process.env.POSTHOG_API_KEY) {
|
||||
if (!runtimeEnv.POSTHOG_API_KEY) {
|
||||
return null;
|
||||
}
|
||||
if (!phClient) {
|
||||
phClient = new PostHog(process.env.POSTHOG_API_KEY, {
|
||||
host: process.env.POSTHOG_HOST || "https://us.i.posthog.com",
|
||||
phClient = new PostHog(runtimeEnv.POSTHOG_API_KEY, {
|
||||
host: runtimeEnv.POSTHOG_HOST || "https://us.i.posthog.com",
|
||||
});
|
||||
}
|
||||
return phClient;
|
||||
@@ -122,7 +123,7 @@ pricingAgentRouter.post("/chat", async (c) => {
|
||||
} = await c.req.json();
|
||||
const ctx = c.var.ctx;
|
||||
|
||||
if (!process.env.ANTHROPIC_API_KEY) {
|
||||
if (!runtimeEnv.ANTHROPIC_API_KEY) {
|
||||
throw new InternalError({
|
||||
message: "ANTHROPIC_API_KEY not configured",
|
||||
code: "anthropic_not_configured",
|
||||
@@ -150,7 +151,7 @@ When the user asks to make changes, modify this existing configuration rather th
|
||||
|
||||
// Create Anthropic client and optionally wrap with PostHog tracing
|
||||
const anthropicClient = createAnthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
apiKey: runtimeEnv.ANTHROPIC_API_KEY,
|
||||
});
|
||||
const baseModel = anthropicClient("claude-opus-4-5");
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { runtimeEnv } from "@/utils/envUtils.js";
|
||||
import { ApiVersion, ApiVersionClass } from "@autumn/shared";
|
||||
import type { Context } from "hono";
|
||||
import { matchRoute } from "../../../honoMiddlewares/middlewareUtils";
|
||||
@@ -224,7 +225,7 @@ export const resolveRateLimit = ({
|
||||
export const RATE_LIMIT_CONFIGS: Record<RateLimitType, RateLimitConfig> = {
|
||||
[RateLimitType.General]: {
|
||||
name: "general",
|
||||
limit: process.env.NODE_ENV === "development" ? 1000 : 25,
|
||||
limit: runtimeEnv.NODE_ENV === "development" ? 1000 : 25,
|
||||
windowMs: 1000,
|
||||
notInRedis: false,
|
||||
scope: RateLimitScope.Org,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user