chittycan
Version:
Your completely autonomous network that grows with you - DNA ownership platform with encrypted vaults, PDX portability, and ChittyFoundation governance
175 lines (151 loc) ⢠5.19 kB
text/typescript
/**
* Doctor command - validate environment and configuration
*/
import { loadConfig, getConfigPath } from "../lib/config.js";
import fs from "fs";
import os from "os";
import { execSync } from "child_process";
interface Check {
name: string;
status: "ā" | "ā" | "ā ";
message: string;
fix?: string;
}
export async function doctor(): Promise<void> {
console.log("\nš ChittyTracker Doctor\n");
const checks: Check[] = [];
// Node.js version
const nodeVersion = process.version;
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
checks.push({
name: "Node.js version",
status: nodeMajor >= 18 ? "ā" : "ā",
message: `${nodeVersion} ${nodeMajor >= 18 ? "(supported)" : "(requires 18+)"}`,
fix: nodeMajor < 18 ? "Install Node.js 18 or later: https://nodejs.org" : undefined
});
// Config file exists
const configPath = getConfigPath();
const configExists = fs.existsSync(configPath);
checks.push({
name: "Config file",
status: configExists ? "ā" : "ā ",
message: configExists ? configPath : "Not found",
fix: !configExists ? "Run: chitty config" : undefined
});
// Load config if exists
let config: any = {};
if (configExists) {
try {
config = loadConfig();
checks.push({
name: "Config valid",
status: "ā",
message: "JSON parseable"
});
} catch (error: any) {
checks.push({
name: "Config valid",
status: "ā",
message: `Parse error: ${error.message}`,
fix: `Edit ${configPath} or delete and run: chitty config`
});
}
}
// Remotes configured
const remoteCount = Object.keys(config.remotes || {}).length;
checks.push({
name: "Remotes",
status: remoteCount > 0 ? "ā" : "ā ",
message: `${remoteCount} configured`,
fix: remoteCount === 0 ? "Run: chitty config ā New remote" : undefined
});
// Shell hooks
const shell = process.env.SHELL || "";
let hooksInstalled = false;
if (shell.includes("zsh")) {
const zshrc = fs.existsSync(os.homedir() + "/.zshrc")
? fs.readFileSync(os.homedir() + "/.zshrc", "utf8")
: "";
hooksInstalled = zshrc.includes(">>> chitty");
} else if (shell.includes("bash")) {
const bashrc = fs.existsSync(os.homedir() + "/.bashrc")
? fs.readFileSync(os.homedir() + "/.bashrc", "utf8")
: "";
hooksInstalled = bashrc.includes(">>> chitty");
}
checks.push({
name: "Shell hooks",
status: hooksInstalled ? "ā" : "ā ",
message: hooksInstalled ? `Installed (${shell})` : "Not installed",
fix: !hooksInstalled ? "Run: chitty hook install zsh" : undefined
});
// Extensions
const extensions = Object.keys(config.extensions || {});
const enabledExt = extensions.filter(e => config.extensions[e]?.enabled !== false);
checks.push({
name: "Extensions",
status: enabledExt.length > 0 ? "ā" : "ā ",
message: `${enabledExt.length}/${extensions.length} enabled`,
fix: extensions.length === 0 ? "Install: npm install @chitty/cloudflare @chitty/neon @chitty/linear" : undefined
});
// Git installed (for hooks)
let gitInstalled = false;
try {
execSync("git --version", { stdio: "ignore" });
gitInstalled = true;
} catch {}
checks.push({
name: "Git",
status: gitInstalled ? "ā" : "ā ",
message: gitInstalled ? "Installed" : "Not found",
fix: !gitInstalled ? "Install git for post-commit hooks" : undefined
});
// Environment tokens
const tokenChecks = [
{ name: "NOTION_TOKEN", env: "NOTION_TOKEN", configPath: "sync.notionToken" },
{ name: "GITHUB_TOKEN", env: "GITHUB_TOKEN", configPath: "sync.githubToken" },
];
for (const tc of tokenChecks) {
const envSet = !!process.env[tc.env];
const configSet = !!(tc.configPath && config.sync && getNestedValue(config, tc.configPath));
if (envSet || configSet) {
checks.push({
name: tc.name,
status: "ā",
message: envSet ? "Set in environment" : "Set in config"
});
} else {
checks.push({
name: tc.name,
status: "ā ",
message: "Not configured",
fix: `Set ${tc.env} environment variable or run: chitty sync setup`
});
}
}
// Print results
checks.forEach(check => {
console.log(` ${check.status} ${check.name}: ${check.message}`);
if (check.fix) {
console.log(` ā ${check.fix}`);
}
});
// Summary
const passed = checks.filter(c => c.status === "ā").length;
const warnings = checks.filter(c => c.status === "ā ").length;
const failed = checks.filter(c => c.status === "ā").length;
console.log();
console.log(`Summary: ${passed} passed, ${warnings} warnings, ${failed} failed`);
if (failed > 0) {
console.log("\nā Some checks failed. Please fix the issues above.");
process.exit(1);
} else if (warnings > 0) {
console.log("\nā ļø Some optional features not configured.");
} else {
console.log("\nā
Everything looks good!");
}
console.log();
}
function getNestedValue(obj: any, path: string): any {
return path.split('.').reduce((acc, part) => acc?.[part], obj);
}