@awesomate/hosting-mcp
Version:
Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases
103 lines (93 loc) • 3.33 kB
JavaScript
/**
* Version-control health check for an Awesomate app folder.
*
* Run from (or pass) the project directory. Prints a JSON report Claude uses
* to keep the user's version control healthy WITHOUT them knowing Git:
* - repo/remote wiring, uncommitted + unpushed work
* - branch → deploy-env mapping (dev → awesomate.dev, main → live)
* - last GitHub Actions deploy run (when `gh` is authed)
*
* Exit code 0 always (reporting tool, not a gate) — Claude decides what to
* fix based on the JSON.
*/
import { execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { resolve } from 'node:path';
const dir = resolve(process.argv[2] ?? '.');
const report = {
dir,
gitInstalled: false,
repoInitialized: false,
remote: null,
branch: null,
uncommittedFiles: 0,
unpushedCommits: null, // null = unknown (no upstream)
hasUpstream: false,
envTracked: false,
ghAuthed: false,
lastDeployRun: null,
problems: [],
};
function sh(cmd) {
return execSync(cmd, { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000 })
.toString()
.trim();
}
function trySh(cmd) {
try {
return sh(cmd);
} catch {
return null;
}
}
if (trySh('git --version') === null) {
report.problems.push('git_not_installed');
} else {
report.gitInstalled = true;
report.repoInitialized = trySh('git rev-parse --is-inside-work-tree') === 'true';
if (!report.repoInitialized) {
report.problems.push('no_repo');
} else {
report.branch = trySh('git branch --show-current') || '(detached)';
report.remote = trySh('git remote get-url origin');
if (!report.remote) report.problems.push('no_remote');
const status = trySh('git status --porcelain') ?? '';
report.uncommittedFiles = status ? status.split('\n').length : 0;
if (report.uncommittedFiles > 0) report.problems.push('uncommitted_changes');
// .env must never be tracked — the single most dangerous drift.
const tracked = trySh('git ls-files .env */.env') ?? '';
report.envTracked = tracked.length > 0;
if (report.envTracked) report.problems.push('env_file_tracked');
const upstream = trySh('git rev-parse --abbrev-ref @{upstream}');
report.hasUpstream = !!upstream;
if (upstream) {
const ahead = trySh('git rev-list --count @{upstream}..HEAD');
report.unpushedCommits = ahead === null ? null : Number(ahead);
if (report.unpushedCommits > 0) report.problems.push('unpushed_commits');
} else if (report.remote) {
report.problems.push('branch_not_pushed');
}
if (report.remote && trySh('gh auth status') !== null) {
report.ghAuthed = true;
const runs = trySh(
'gh run list --limit 1 --json displayTitle,conclusion,headBranch,updatedAt',
);
try {
const parsed = JSON.parse(runs ?? '[]');
if (parsed[0]) {
report.lastDeployRun = parsed[0];
if (parsed[0].conclusion && parsed[0].conclusion !== 'success') {
report.problems.push('last_deploy_failed');
}
}
} catch {
/* no runs / not parseable — fine */
}
}
}
}
if (!existsSync(resolve(dir, '.gitignore')) && report.repoInitialized) {
report.problems.push('no_gitignore');
}
console.log(JSON.stringify(report, null, 2));