@accounter/server
Version:
The test suite is split into three Vitest projects for efficiency and isolation:
233 lines (212 loc) • 7.21 kB
text/typescript
import { config as dotenv } from 'dotenv';
import zod from 'zod';
// Prefer isolated test env file when provided, otherwise fall back to repo-level .env
dotenv({
path:
process.env.TEST_ENV_FILE && process.env.TEST_ENV_FILE.trim() !== ''
? process.env.TEST_ENV_FILE
: '../../.env',
debug: process.env.RELEASE ? false : true,
});
const isNumberString = (input: unknown) => zod.string().regex(/^\d+$/).safeParse(input).success;
const numberFromNumberOrNumberString = (input: unknown): number | undefined => {
if (typeof input === 'number') return input;
if (isNumberString(input)) return Number(input);
return undefined;
};
const NumberFromString = zod.preprocess(numberFromNumberOrNumberString, zod.number().min(1));
// treat an empty string (`''`) as undefined
const emptyString = <T extends zod.ZodType>(input: T) => {
return zod.preprocess((value: unknown) => {
if (value === '') return undefined;
return value;
}, input);
};
const PostgresModel = zod.object({
POSTGRES_SSL: emptyString(zod.union([zod.literal('1'), zod.literal('0')]).optional()),
POSTGRES_HOST: zod.string(),
POSTGRES_PORT: NumberFromString,
POSTGRES_DB: zod.string(),
POSTGRES_USER: zod.string(),
POSTGRES_PASSWORD: zod.string(),
});
const CloudinaryModel = zod.union([
zod
.object({
CLOUDINARY_NAME: zod.string().optional(),
CLOUDINARY_API_KEY: zod.string().optional(),
CLOUDINARY_API_SECRET: zod.string().optional(),
})
.superRefine((data, ctx) => {
if (
!!data.CLOUDINARY_NAME !== !!data.CLOUDINARY_API_KEY ||
!!data.CLOUDINARY_NAME !== !!data.CLOUDINARY_API_SECRET
) {
ctx.addIssue({
code: 'custom',
message:
'CLOUDINARY_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET must be provided together.',
});
}
}),
zod.void(),
]);
const GreenInvoiceModel = zod.union([
zod
.object({
GREEN_INVOICE_ID: zod.string().optional(),
GREEN_INVOICE_SECRET: zod.string().optional(),
})
.superRefine((data, ctx) => {
if (!!data.GREEN_INVOICE_ID !== !!data.GREEN_INVOICE_SECRET) {
ctx.addIssue({
code: 'custom',
message: 'GREEN_INVOICE_ID and GREEN_INVOICE_SECRET must be provided together.',
});
}
}),
zod.void(),
]);
const AuthorizationModel = zod.object({
AUTHORIZED_USERS: zod.union([zod.string(), zod.void()]),
DEFAULT_FINANCIAL_ENTITY_ID: zod.string(),
});
const HiveModel = zod.union([
zod.object({
HIVE_TOKEN: zod.string().optional(),
}),
zod.void(),
]);
const GoogleDriveModel = zod.union([
zod.object({
GOOGLE_DRIVE_API_KEY: zod.string().optional(),
}),
zod.void(),
]);
const GmailModel = zod.union([
zod
.object({
GMAIL_CLIENT_ID: zod.string().optional(),
GMAIL_CLIENT_SECRET: zod.string().optional(),
GMAIL_REFRESH_TOKEN: zod.string().optional(),
GMAIL_LABEL_PATH: zod.string().optional(),
GOOGLE_CLOUD_PROJECT_ID: zod.string().optional(),
GOOGLE_APPLICATION_CREDENTIALS: zod.string().optional(),
PUBSUB_TOPIC: zod.string().optional(),
PUBSUB_SUBSCRIPTION: zod.string().optional(),
})
.superRefine((data, ctx) => {
const gmailVars = [
data.GMAIL_CLIENT_ID,
data.GMAIL_CLIENT_SECRET,
data.GMAIL_REFRESH_TOKEN,
data.GOOGLE_CLOUD_PROJECT_ID,
data.GOOGLE_APPLICATION_CREDENTIALS,
];
// GMAIL_LABEL_PATH, PUBSUB_TOPIC and PUBSUB_SUBSCRIPTION are optional and can be omitted
const definedCount = gmailVars.filter(v => !!v).length;
if (definedCount !== 0 && definedCount !== gmailVars.length) {
ctx.addIssue({
code: 'custom',
message:
'Gmail variables (GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REFRESH_TOKEN, GOOGLE_CLOUD_PROJECT_ID, GOOGLE_APPLICATION_CREDENTIALS) must be provided together or all omitted.',
});
}
}),
zod.void(),
]);
const DeelModel = zod.union([
zod.object({
DEEL_TOKEN: zod.string().optional(),
}),
zod.void(),
]);
const configs = {
postgres: PostgresModel.safeParse(process.env),
cloudinary: CloudinaryModel.safeParse(process.env),
greenInvoice: GreenInvoiceModel.safeParse(process.env),
authorization: AuthorizationModel.safeParse(process.env),
hive: HiveModel.safeParse(process.env),
googleDrive: GoogleDriveModel.safeParse(process.env),
gmail: GmailModel.safeParse(process.env),
deel: DeelModel.safeParse(process.env),
};
const environmentErrors: Array<string> = [];
for (const config of Object.values(configs)) {
if (config.success === false) {
environmentErrors.push(JSON.stringify(config.error.format(), null, 4));
}
}
if (environmentErrors.length) {
const fullError = environmentErrors.join(`\n`);
console.error('❌ Invalid environment variables:', fullError);
process.exit(1);
}
function extractConfig<Output>(config: zod.ZodSafeParseResult<Output>): Output {
if (!config.success) {
throw new Error('Something went wrong.');
}
return config.data;
}
const postgres = extractConfig(configs.postgres);
const cloudinary = extractConfig(configs.cloudinary);
const greenInvoice = extractConfig(configs.greenInvoice);
const authorization = extractConfig(configs.authorization);
const hive = extractConfig(configs.hive);
const googleDrive = extractConfig(configs.googleDrive);
const gmail = extractConfig(configs.gmail);
const deel = extractConfig(configs.deel);
export const env = {
postgres: {
host: postgres.POSTGRES_HOST,
port: postgres.POSTGRES_PORT,
db: postgres.POSTGRES_DB,
user: postgres.POSTGRES_USER,
password: postgres.POSTGRES_PASSWORD,
ssl: postgres.POSTGRES_SSL === '1',
},
cloudinary: cloudinary?.CLOUDINARY_API_KEY
? {
name: cloudinary.CLOUDINARY_NAME!,
apiKey: cloudinary.CLOUDINARY_API_KEY!,
apiSecret: cloudinary.CLOUDINARY_API_SECRET!,
}
: undefined,
greenInvoice: greenInvoice?.GREEN_INVOICE_ID
? {
id: greenInvoice.GREEN_INVOICE_ID!,
secret: greenInvoice.GREEN_INVOICE_SECRET!,
}
: undefined,
authorization: {
users: authorization?.AUTHORIZED_USERS,
adminBusinessId: authorization?.DEFAULT_FINANCIAL_ENTITY_ID,
},
hive: hive?.HIVE_TOKEN
? {
hiveToken: hive.HIVE_TOKEN!,
}
: undefined,
googleDrive: googleDrive?.GOOGLE_DRIVE_API_KEY
? {
driveApiKey: googleDrive.GOOGLE_DRIVE_API_KEY!,
}
: undefined,
deel: deel?.DEEL_TOKEN
? {
apiToken: deel.DEEL_TOKEN!,
}
: undefined,
gmail: gmail?.GMAIL_CLIENT_ID
? {
clientId: gmail.GMAIL_CLIENT_ID!,
clientSecret: gmail.GMAIL_CLIENT_SECRET!,
refreshToken: gmail.GMAIL_REFRESH_TOKEN!,
labelPath: gmail.GMAIL_LABEL_PATH?.replace(/\/$/, '') || 'accounter/documents', // Default label if not specified
cloudProjectId: gmail.GOOGLE_CLOUD_PROJECT_ID!,
appCredentials: gmail.GOOGLE_APPLICATION_CREDENTIALS!,
topicName: gmail.PUBSUB_TOPIC || 'gmail-notifications',
subscriptionName: gmail.PUBSUB_SUBSCRIPTION || 'gmail-notifications-sub',
}
: undefined,
} as const;