impeccable-paths.mjs

  1import fs from 'node:fs';
  2import path from 'node:path';
  3
  4export const IMPECCABLE_DIR = '.impeccable';
  5export const LIVE_DIR = 'live';
  6export const CRITIQUE_DIR = 'critique';
  7
  8export function getImpeccableDir(cwd = process.cwd()) {
  9  return path.join(cwd, IMPECCABLE_DIR);
 10}
 11
 12export function getDesignSidecarPath(cwd = process.cwd()) {
 13  return path.join(getImpeccableDir(cwd), 'design.json');
 14}
 15
 16export function getDesignSidecarCandidates(cwd = process.cwd(), contextDir = cwd) {
 17  const candidates = [
 18    getDesignSidecarPath(cwd),
 19    path.join(cwd, 'DESIGN.json'),
 20  ];
 21  const contextLegacy = path.join(contextDir, 'DESIGN.json');
 22  if (!candidates.includes(contextLegacy)) candidates.push(contextLegacy);
 23  return candidates;
 24}
 25
 26export function resolveDesignSidecarPath(cwd = process.cwd(), contextDir = cwd) {
 27  return firstExisting(getDesignSidecarCandidates(cwd, contextDir));
 28}
 29
 30export function getLiveDir(cwd = process.cwd()) {
 31  return path.join(getImpeccableDir(cwd), LIVE_DIR);
 32}
 33
 34export function getLiveConfigPath(cwd = process.cwd()) {
 35  return path.join(getLiveDir(cwd), 'config.json');
 36}
 37
 38export function getLegacyLiveConfigPath(scriptsDir) {
 39  return path.join(scriptsDir, 'config.json');
 40}
 41
 42export function resolveLiveConfigPath({ cwd = process.cwd(), scriptsDir, env = process.env } = {}) {
 43  if (env.IMPECCABLE_LIVE_CONFIG && env.IMPECCABLE_LIVE_CONFIG.trim()) {
 44    const configured = env.IMPECCABLE_LIVE_CONFIG.trim();
 45    return path.isAbsolute(configured) ? configured : path.resolve(cwd, configured);
 46  }
 47  const primary = getLiveConfigPath(cwd);
 48  if (fs.existsSync(primary)) return primary;
 49  if (scriptsDir) {
 50    const legacy = getLegacyLiveConfigPath(scriptsDir);
 51    if (fs.existsSync(legacy)) return legacy;
 52  }
 53  return primary;
 54}
 55
 56export function getLiveServerPath(cwd = process.cwd()) {
 57  return path.join(getLiveDir(cwd), 'server.json');
 58}
 59
 60export function getLegacyLiveServerPath(cwd = process.cwd()) {
 61  return path.join(cwd, '.impeccable-live.json');
 62}
 63
 64export function readLiveServerInfo(cwd = process.cwd()) {
 65  for (const filePath of [getLiveServerPath(cwd), getLegacyLiveServerPath(cwd)]) {
 66    try {
 67      return { info: JSON.parse(fs.readFileSync(filePath, 'utf-8')), path: filePath };
 68    } catch {
 69      /* try next */
 70    }
 71  }
 72  return null;
 73}
 74
 75export function writeLiveServerInfo(cwd = process.cwd(), info) {
 76  const filePath = getLiveServerPath(cwd);
 77  fs.mkdirSync(path.dirname(filePath), { recursive: true });
 78  fs.writeFileSync(filePath, JSON.stringify(info));
 79  return filePath;
 80}
 81
 82export function removeLiveServerInfo(cwd = process.cwd()) {
 83  for (const filePath of [getLiveServerPath(cwd), getLegacyLiveServerPath(cwd)]) {
 84    try { fs.unlinkSync(filePath); } catch {}
 85  }
 86}
 87
 88export function getLiveSessionsDir(cwd = process.cwd()) {
 89  return path.join(getLiveDir(cwd), 'sessions');
 90}
 91
 92export function getLegacyLiveSessionsDir(cwd = process.cwd()) {
 93  return path.join(cwd, '.impeccable-live', 'sessions');
 94}
 95
 96export function getLiveAnnotationsDir(cwd = process.cwd()) {
 97  return path.join(getLiveDir(cwd), 'annotations');
 98}
 99
100export function getCritiqueDir(cwd = process.cwd()) {
101  return path.join(getImpeccableDir(cwd), CRITIQUE_DIR);
102}
103
104export function getLegacyLiveAnnotationsDir(cwd = process.cwd()) {
105  return path.join(cwd, '.impeccable-live', 'annotations');
106}
107
108function firstExisting(paths) {
109  return paths.find((filePath) => fs.existsSync(filePath)) || null;
110}