UNPKG

@awesomate/hosting-mcp

Version:

Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases

116 lines (104 loc) • 5.42 kB
#!/usr/bin/env node /** * Zero-knowledge GitHub scaffolder. Idempotent — safe to re-run. * * node github-setup.mjs <repo-name> [--public] [--path <dir>] * * Steps: verify gh auth → ensure .gitignore excludes .env → SECRET GUARD * (refuse to commit a .env or an obvious key/token) → git init + first commit → * `gh repo create --source=. --push` (private by default; personal accounts OK). * * Deploy secrets are NOT handled here: run deploy-key.mjs <appId> afterwards, * which mints a per-app key and sets SSH_HOST/SSH_USER/SSH_PRIVATE_KEY itself. * Prints only non-sensitive output. */ import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync, statSync } from 'node:fs'; import { join } from 'node:path'; const args = process.argv.slice(2); const flags = new Set(args.filter((a) => a.startsWith('--'))); const positional = args.filter((a) => !a.startsWith('--')); const repoName = positional[0]; const pathIdx = args.indexOf('--path'); const cwd = pathIdx >= 0 && args[pathIdx + 1] ? args[pathIdx + 1] : process.cwd(); const isPublic = flags.has('--public'); if (!repoName || !/^[a-z0-9][a-z0-9._-]{0,99}$/i.test(repoName)) { console.error('usage: node github-setup.mjs <repo-name> [--public] [--path <dir>]'); console.error('repo-name: letters/digits/._- , starts alphanumeric'); process.exit(2); } function run(cmd, cmdArgs, opts = {}) { return spawnSync(cmd, cmdArgs, { cwd, encoding: 'utf8', ...opts }); } function die(msg) { console.error(`\n✗ ${msg}`); process.exit(1); } // --- 0. gh present + authenticated ---------------------------------------- if (run('gh', ['--version']).status !== 0) { die("GitHub CLI 'gh' isn't installed. Install it (macOS: brew install gh) then re-run."); } if (run('gh', ['auth', 'status']).status !== 0) { die("You're not connected to GitHub yet. Run: gh auth login (GitHub.com → HTTPS → Login with a web browser), then re-run this."); } // --- 1. .gitignore excludes .env ------------------------------------------ const giPath = join(cwd, '.gitignore'); // .awesomate/ is the secret-drop inbox — the credentials skill tells users it is // ignored, and the SECRET_RE guard only catches known key SHAPES, so a database // URL or bare token in there would otherwise be committable. const needed = ['.env', '.env.local', '.awesomate/', '*.log', '.DS_Store', 'node_modules/']; let gi = existsSync(giPath) ? readFileSync(giPath, 'utf8') : ''; const have = new Set(gi.split(/\r?\n/).map((l) => l.trim())); const toAdd = needed.filter((n) => !have.has(n)); if (toAdd.length) { gi = (gi.endsWith('\n') || gi === '' ? gi : gi + '\n') + toAdd.join('\n') + '\n'; writeFileSync(giPath, gi); console.log(`• .gitignore: added ${toAdd.join(', ')}`); } // --- init early so the secret guard can inspect the staged set ------------ if (!existsSync(join(cwd, '.git'))) { if (run('git', ['init', '-b', 'main']).status !== 0) die('git init failed.'); console.log('• initialised a git repo (branch: main)'); } if (run('git', ['add', '-A']).status !== 0) die('git add failed.'); // --- 2. SECRET GUARD ------------------------------------------------------- const staged = run('git', ['diff', '--cached', '--name-only']).stdout.split(/\r?\n/).filter(Boolean); const envLeak = staged.find((f) => /(^|\/)\.env(\.|$)/.test(f) && !/\.env\.example$/.test(f)); if (envLeak) { die(`Refusing to commit '${envLeak}' — that's a secrets file. It's now gitignored; run \`git rm --cached ${envLeak}\` and re-run. Store secrets via the awesomate-credentials skill, never in the repo.`); } const SECRET_RE = /(sk-[A-Za-z0-9]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|xox[bp]-[A-Za-z0-9-]{10,}|-----BEGIN [A-Z ]*PRIVATE KEY-----)/; for (const f of staged) { const abs = join(cwd, f); try { if (statSync(abs).size > 512 * 1024) continue; // skip large/binary if (SECRET_RE.test(readFileSync(abs, 'utf8'))) { die(`Refusing to commit '${f}' — it contains something shaped like a secret (API key / token / private key). Move it out (use the awesomate-credentials skill) and re-run.`); } } catch { /* unreadable/binary — skip */ } } // --- 3. first commit (only if there's anything to commit) ----------------- if (run('git', ['diff', '--cached', '--quiet']).status !== 0) { if (run('git', ['commit', '-m', 'Initial commit']).status !== 0) die('git commit failed.'); console.log('• made the first commit'); } else { console.log('• nothing new to commit'); } // --- 4. create the GitHub repo + push ------------------------------------- const already = run('git', ['remote', 'get-url', 'origin']); if (already.status === 0) { console.log(`• remote already set: ${already.stdout.trim()}`); run('git', ['push', '-u', 'origin', 'HEAD']); } else { const vis = isPublic ? '--public' : '--private'; const create = run('gh', ['repo', 'create', repoName, vis, '--source=.', '--remote=origin', '--push']); if (create.status !== 0) { die(`gh repo create failed: ${(create.stderr || create.stdout || '').trim()}`); } console.log(`• created ${isPublic ? 'public' : 'private'} repo and pushed`); } // --- done ------------------------------------------------------------------ const url = run('gh', ['repo', 'view', '--json', 'url', '-q', '.url']).stdout.trim(); console.log(`\n✓ Done. Your project is on GitHub${url ? `: ${url}` : ''}.`);