const path = require('node:path'); const os = require('node:os'); const fs = require('node:fs'); const CONFIG_MODE = 0o600; const DIR_MODE = 0o700; // Paths are resolved lazily (on every call) so that the global `--config` // flag — which sets STOKE_CONFIG_FILE from a preAction hook — takes effect // even though this module is required before the CLI parses its arguments. function getConfigDir() { if (process.env.STOKE_CONFIG_DIR) return process.env.STOKE_CONFIG_DIR; if (process.env.FORGEJO_CONFIG_DIR) return process.env.FORGEJO_CONFIG_DIR; // Per the XDG spec, $XDG_CONFIG_HOME already points at the config root // (it replaces ~/.config, it does not live inside it). if (process.env.XDG_CONFIG_HOME) return path.join(process.env.XDG_CONFIG_HOME, 'stoke'); return path.join(os.homedir(), '.config', 'stoke'); } function getConfigPath() { return process.env.STOKE_CONFIG_FILE || process.env.FORGEJO_CONFIG_FILE || path.join(getConfigDir(), 'config.json'); } function ensureConfigDir() { fs.mkdirSync(path.dirname(getConfigPath()), { recursive: true, mode: DIR_MODE }); } function loadConfig() { const configPath = getConfigPath(); try { const raw = fs.readFileSync(configPath, 'utf8'); return JSON.parse(raw); } catch (err) { if (err.code === 'ENOENT') return null; throw new Error(`Failed to read config at ${configPath}: ${err.message}`); } } function saveConfig(config) { ensureConfigDir(); const configPath = getConfigPath(); const tmp = `${configPath}.tmp`; fs.writeFileSync(tmp, JSON.stringify(config, null, 2), { mode: CONFIG_MODE }); fs.renameSync(tmp, configPath); try { fs.chmodSync(configPath, CONFIG_MODE); } catch { // ignore on platforms where chmod is unsupported } } function clearConfig() { try { fs.unlinkSync(getConfigPath()); } catch (err) { if (err.code !== 'ENOENT') throw err; } } module.exports = { getConfigDir, getConfigPath, loadConfig, saveConfig, clearConfig, };