import * as path from 'path'; import chalk from 'chalk'; import { readConfig, readState, getProjectRoot } from '../config'; import { ApiClient } from '../api'; import { findEndpointDirs, readEndpointFromDisk } from '../files'; import { computeEndpointHash } from '../hash'; export async function statusCommand(): Promise { const root = getProjectRoot(); const config = readConfig(root); const state = readState(root); const api = new ApiClient(config); if (!state.last_sync) { console.log(chalk.yellow('No sync history. Run "kisync pull" first.')); return; } console.log(chalk.gray(`Last sync: ${state.last_sync}\n`)); // 1) Detect local changes const localModified: string[] = []; const localNew: string[] = []; const endpointDirs = findEndpointDirs(root); const knownIds = new Set(Object.keys(state.endpoints)); const foundIds = new Set(); for (const dir of endpointDirs) { const ep = readEndpointFromDisk(dir); if (!ep) continue; if (ep.id && knownIds.has(ep.id)) { foundIds.add(ep.id); const stateEntry = state.endpoints[ep.id]; if (stateEntry && stateEntry.hash) { const currentHash = computeEndpointHash(ep); if (currentHash !== stateEntry.hash) { localModified.push(`${path.relative(root, dir)} (${ep.name})`); } } } else if (!ep.id) { // New local endpoint (no id assigned yet) localNew.push(`${path.relative(root, dir)} (${ep.name || 'unnamed'})`); } } // Locally deleted (existed in state but no longer on disk) const localDeleted: string[] = []; for (const [id, info] of Object.entries(state.endpoints)) { if (!foundIds.has(id)) { localDeleted.push(`${info.folder_path}`); } } // 2) Check server changes const clientEndpoints = Object.entries(state.endpoints).map(([id, info]) => ({ id, updated_at: info.updated_at, })); const clientFolders = Object.entries(state.folders).map(([id, info]) => ({ id, updated_at: info.updated_at, })); console.log(chalk.gray('Checking server...')); const serverStatus = await api.status({ endpoints: clientEndpoints, folders: clientFolders }); // 3) Display results const hasLocalChanges = localModified.length > 0 || localNew.length > 0 || localDeleted.length > 0; const hasServerChanges = serverStatus.endpoints.changed.length > 0 || serverStatus.endpoints.new.length > 0 || serverStatus.endpoints.deleted.length > 0; if (!hasLocalChanges && !hasServerChanges) { console.log(chalk.green('\nEverything is in sync.')); return; } // Local changes if (hasLocalChanges) { console.log(chalk.bold('\nLocal changes (not pushed):')); for (const item of localModified) { console.log(chalk.yellow(` modified: ${item}`)); } for (const item of localNew) { console.log(chalk.green(` new: ${item}`)); } for (const item of localDeleted) { console.log(chalk.red(` deleted: ${item}`)); } } // Server changes if (hasServerChanges) { console.log(chalk.bold('\nServer changes (not pulled):')); for (const item of serverStatus.endpoints.changed) { console.log(chalk.blue(` modified: ${item.name}`)); } for (const item of serverStatus.endpoints.new) { console.log(chalk.green(` new: ${item.name}`)); } for (const item of serverStatus.endpoints.deleted) { console.log(chalk.red(` deleted: id=${item.id}`)); } } // Conflicts warning if (hasLocalChanges && hasServerChanges) { console.log(chalk.yellow('\nBoth local and server have changes!')); console.log(chalk.gray(' Push first to send your changes, then pull to get server updates.')); console.log(chalk.gray(' Or use --force on either to overwrite.')); } else if (hasLocalChanges) { console.log(chalk.gray('\nRun "kisync push" to upload your changes.')); } else { console.log(chalk.gray('\nRun "kisync pull" to download server changes.')); } }