actual-moneymoney
Version:
An importer for syncing MoneyMoney accounts and transactions to Actual.
109 lines (108 loc) • 4.19 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import fs from 'fs/promises';
import path from 'path';
import toml from 'toml';
import { ZodIssueCode, z } from 'zod';
import { DEFAULT_CONFIG_FILE } from './shared.js';
const budgetSchema = z
.object({
syncId: z.string(),
earliestImportDate: z.string().optional(),
e2eEncryption: z.object({
enabled: z.boolean(),
password: z.string().optional(),
}),
accountMapping: z.record(z.string()),
})
.superRefine((val, ctx) => {
if (val.e2eEncryption.enabled && !val.e2eEncryption.password) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: 'Password must not be empty if end-to-end encryption is enabled',
});
}
if (val.earliestImportDate) {
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(val.earliestImportDate)) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: 'Invalid earliest import date format (required format is YYYY-MM-DD)',
});
}
}
return val;
});
const actualServerSchema = z.object({
serverUrl: z.string(),
serverPassword: z.string(),
budgets: z.array(budgetSchema).min(1),
});
const payeeTransformationSchema = z.object({
enabled: z.boolean(),
openAiApiKey: z.string().optional(),
openAiModel: z.string().optional().default('gpt-3.5-turbo'),
});
export const configSchema = z
.object({
payeeTransformation: payeeTransformationSchema,
import: z.object({
importUncheckedTransactions: z.boolean(),
synchronizeClearedStatus: z.boolean().default(true),
ignorePatterns: z
.object({
commentPatterns: z.array(z.string()).optional(),
payeePatterns: z.array(z.string()).optional(),
purposePatterns: z.array(z.string()).optional(),
})
.optional(),
}),
actualServers: z.array(actualServerSchema).min(1),
})
.superRefine((val, ctx) => {
// Check openAI key if payeeTransformation is enabled
if (val.payeeTransformation.enabled &&
!val.payeeTransformation.openAiApiKey) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: 'OpenAI key must not be empty if payeeTransformation is enabled',
});
}
});
export const getConfigFile = (argv) => {
if (argv.config) {
const argvConfigFile = path.resolve(argv.config);
return argvConfigFile;
}
return DEFAULT_CONFIG_FILE;
};
export const getConfig = (argv) => __awaiter(void 0, void 0, void 0, function* () {
const configFile = getConfigFile(argv);
const configFileExists = yield fs
.access(configFile)
.then(() => true)
.catch(() => false);
if (!configFileExists) {
throw new Error(`Config file not found: '${configFile}'. Create it or use the --config option to specify a different path.`);
}
const configContent = yield fs.readFile(configFile, 'utf-8');
try {
const configData = toml.parse(configContent);
return configSchema.parse(configData);
}
catch (e) {
if (e instanceof Error && e.name === 'SyntaxError') {
const line = 'line' in e ? e.line : -1;
const column = 'column' in e ? e.column : -1;
throw new Error(`Failed to parse configuration file: ${e.message} (line ${line}, column ${column})`);
}
throw new Error(`Invalid configuration file format. Run 'validate' to see errors.`);
}
});