All files config.ts

33.33% Statements 9/27
66.66% Branches 12/18
75% Functions 3/4
29.16% Lines 7/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                                                          1x                                                                                           13x 1x           13x       13x 13x       13x                      
import { readFileSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { DEFAULT_CACHE_FILENAME } from './cache.js';
import { homedir } from 'node:os';
 
export interface PluginEntry {
    /** npm package name or local path relative to contractkit.config.json */
    plugin: string;
    /**
     * Plugin-specific options passed as ctx.options.
     *
     * One reserved key: `keys?: Record<string, string>`. Entries are merged across all plugin
     * entries into a workspace-wide fallback map for `{{var}}` substitution in `.ck` files.
     * A file's `options { keys }` block still wins over this fallback when both define a name.
     */
    options?: Record<string, unknown>;
}
 
/** Record-keyed plugin config: each key is the plugin package name, value is options. */
export type PluginsConfig = Record<string, Record<string, unknown>>;
 
export interface DslConfig {
    rootDir?: string;
    cache?: boolean | string;
    /** Glob patterns for .ck files to compile, relative to rootDir. */
    patterns?: string[];
    /** Run prettier on generated TypeScript files after compilation. Default: false. */
    prettier?: boolean;
    /** Plugins to load: each key is the plugin package name, value is its options. */
    plugins?: PluginsConfig;
}
 
export interface ResolvedCacheConfig {
    enabled: boolean;
    filename: string;
}
 
const CONFIG_FILENAME = 'contractkit.config.json';
 
/**
 * Load config from an explicit path, or search upward from `startDir`
 * for contractkit.config.json.
 */
export function loadConfig(configPath?: string, startDir: string = process.cwd()): { config: DslConfig; configDir: string } {
    if (configPath) {
        const resolved = resolve(configPath);
        try {
            const text = readFileSync(resolved, 'utf-8');
            return { config: JSON.parse(text) as DslConfig, configDir: dirname(resolved) };
        } catch (err) {
            throw new Error(`Failed to load config from ${resolved}: ${(err as Error).message}`, { cause: err });
        }
    }
 
    let dir = resolve(startDir);
    while (true) {
        const candidate = join(dir, CONFIG_FILENAME);
        try {
            const text = readFileSync(candidate, 'utf-8');
            return { config: JSON.parse(text) as DslConfig, configDir: dir };
        } catch {
            // File not found or invalid -- walk up
        }
        const parent = dirname(dir);
        if (parent === dir) break;
        dir = parent;
    }
 
    return { config: {}, configDir: resolve(startDir) };
}
 
export interface ResolvedConfig {
    patterns: string[];
    rootDir: string;
    cache: ResolvedCacheConfig;
    watch: boolean;
    force: boolean;
    prettier: boolean;
    plugins: PluginEntry[];
    configDir: string;
}
 
function normalizePlugins(plugins: PluginsConfig | undefined): PluginEntry[] {
    if (!plugins) return [];
    return Object.entries(plugins).map(([name, options]) => ({ plugin: name, options }));
}
 
/** Merge config file values with CLI flags. */
export function mergeConfig(config: DslConfig, cliArgs: { watch: boolean; force: boolean }, configDir: string = process.cwd()): ResolvedConfig {
    const cache: ResolvedCacheConfig =
        typeof config.cache === 'string'
            ? { enabled: true, filename: config.cache }
            : { enabled: config.cache === true, filename: DEFAULT_CACHE_FILENAME };
 
    let rootDir = config.rootDir ?? '.';
    Iif (rootDir.startsWith('~')) {
        rootDir = homedir() + rootDir.slice(1);
    }
 
    return {
        patterns: config.patterns ?? [],
        rootDir: resolve(rootDir),
        cache,
        watch: cliArgs.watch,
        force: cliArgs.force,
        prettier: config.prettier ?? false,
        plugins: normalizePlugins(config.plugins),
        configDir,
    };
}