CLI tool for syncing local folders with KIS API Builder server. Commands: init, pull, push, status with conflict detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
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<void> {
|
|
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<string>();
|
|
|
|
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.'));
|
|
}
|
|
}
|