@t1mmen/srtd
Version:
Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀
123 lines • 4.59 kB
JavaScript
import fs from 'node:fs/promises';
import path from 'node:path';
import { CONFIG_FILE, DEFAULT_PG_CONNECTION } from '../constants.js';
import { fileExists } from './fileExists.js';
import { logger } from './logger.js';
import { CLIConfigSchema, formatZodErrors } from './schemas.js';
const defaultConfig = {
wipIndicator: '.wip',
migrationPrefix: 'srtd',
migrationFilename: '$timestamp_$prefix$migrationName.sql',
filter: '**/*.sql',
banner: 'You very likely **DO NOT** want to manually edit this generated file.',
footer: '',
wrapInTransaction: true,
templateDir: 'supabase/migrations-templates',
migrationDir: 'supabase/migrations',
buildLog: 'supabase/migrations-templates/.srtd.buildlog.json',
localBuildLog: 'supabase/migrations-templates/.srtd.buildlog.local.json',
pgConnection: DEFAULT_PG_CONNECTION,
};
/**
* Per-directory config cache to support multiple projects in same process
* Caches both config and warnings together
*/
const configCache = new Map();
/**
* Get config for a specific directory.
* Caches per directory to support multi-project scenarios.
* Validates config with Zod schema, falling back to defaults on errors.
* Returns both config and any validation warnings encountered.
*/
export async function getConfig(dir = process.cwd()) {
const resolvedDir = path.resolve(dir);
const cached = configCache.get(resolvedDir);
if (cached)
return cached;
let config;
const warnings = [];
try {
const configPath = path.join(resolvedDir, CONFIG_FILE);
const content = await fs.readFile(configPath, 'utf-8');
// Parse JSON first
let parsed;
try {
parsed = JSON.parse(content);
}
catch (parseError) {
const errorMsg = `Invalid JSON in config file: ${parseError instanceof Error ? parseError.message : 'Parse error'}`;
logger.warn(errorMsg);
warnings.push({
source: 'config',
type: 'parse',
message: errorMsg,
path: configPath,
});
config = { ...defaultConfig };
const result = { config, warnings };
configCache.set(resolvedDir, result);
return result;
}
// Merge with defaults first, then validate the merged result
const merged = { ...defaultConfig, ...parsed };
// Validate the merged config
const validationResult = CLIConfigSchema.safeParse(merged);
if (!validationResult.success) {
const errorMsg = `Invalid config schema: ${formatZodErrors(validationResult.error)}`;
logger.warn(errorMsg);
warnings.push({
source: 'config',
type: 'validation',
message: errorMsg,
path: configPath,
});
config = { ...defaultConfig };
}
else {
config = validationResult.data;
}
}
catch {
// File not found or other read error - use defaults silently
config = { ...defaultConfig };
}
// Check if template directory exists
const templateDirPath = path.join(resolvedDir, config.templateDir);
const templateDirExists = await fileExists(templateDirPath);
if (!templateDirExists) {
warnings.push({
source: 'config',
type: 'missing',
message: `Template directory does not exist: ${config.templateDir}`,
path: templateDirPath,
});
}
const result = { config, warnings };
configCache.set(resolvedDir, result);
return result;
}
/**
* Clear the config cache (useful for testing)
*/
export function clearConfigCache() {
configCache.clear();
}
export async function saveConfig(baseDir, config) {
const resolvedDir = path.resolve(baseDir);
const configPath = path.join(resolvedDir, CONFIG_FILE);
const finalConfig = { ...defaultConfig, ...config };
// Add newline at the end to satisfy linters
await fs.writeFile(configPath, `${JSON.stringify(finalConfig, null, 2)}\n`).catch(e => {
logger.error(`Failed to save config: ${e.message}`);
});
configCache.set(resolvedDir, { config: finalConfig, warnings: [] });
}
export async function resetConfig(baseDir) {
const resolvedDir = path.resolve(baseDir);
configCache.delete(resolvedDir);
await fs.unlink(path.join(resolvedDir, CONFIG_FILE)).catch(() => {
/* ignore */
});
await saveConfig(resolvedDir, {});
}
//# sourceMappingURL=config.js.map