UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

153 lines 4.54 kB
/** * Git Adapter * * Base adapter for fetching packages from any Git URL. * Handles clone to cache, pull for refresh, and version tag checkout. * * Cache layout: * ~/.cache/aiwg/packages/<owner>/<name>@<version>/ * * @implements #557 */ import { execFile } from 'child_process'; import { promisify } from 'util'; import { mkdir, readFile } from 'fs/promises'; import { join } from 'path'; import { homedir } from 'os'; import { existsSync } from 'fs'; const execFileAsync = promisify(execFile); /** * Default cache root */ function getCacheRoot() { const xdgCache = process.env.XDG_CACHE_HOME; const base = xdgCache ? xdgCache : join(homedir(), '.cache'); return join(base, 'aiwg', 'packages'); } /** * Build cache path for a package at a specific version */ export function buildCachePath(owner, name, version) { const safe = version.replace(/[^a-zA-Z0-9._-]/g, '_'); return join(getCacheRoot(), owner, `${name}@${safe}`); } /** * Run a git command, returning stdout */ async function git(args, cwd) { const env = { ...process.env }; // Suppress interactive prompts env.GIT_TERMINAL_PROMPT = '0'; const { stdout } = await execFileAsync('git', args, { cwd, env, timeout: 120_000, }); return stdout.trim(); } /** * Detect the manifest type from a cloned package directory */ async function detectManifestType(cachePath) { const manifestPath = join(cachePath, 'manifest.json'); try { const content = await readFile(manifestPath, 'utf-8'); const manifest = JSON.parse(content); const t = manifest.type?.toLowerCase() ?? ''; if (t === 'framework') return 'framework'; if (t === 'addon') return 'addon'; if (t === 'extension') return 'extension'; return 'unknown'; } catch { return 'unknown'; } } /** * Resolve the latest tag from a remote git repo */ async function resolveLatestTag(gitUrl) { try { const output = await git(['ls-remote', '--tags', '--sort=-v:refname', gitUrl]); const firstLine = output.split('\n')[0] ?? ''; const match = firstLine.match(/refs\/tags\/(.+)/); if (match && match[1]) return match[1].replace(/\^{}$/, ''); } catch { // fall through } return 'latest'; } /** * GitAdapter * * Handles any https:// or git@... URL directly. * Also serves as the base class for Gitea/GitHub shorthand adapters. */ export class GitAdapter { id = 'git'; name = 'Git (direct URL)'; /** * Returns true for https:// or git@/ssh:// URLs, or git+https:// URLs */ canResolve(ref) { return (ref.startsWith('https://') || ref.startsWith('http://') || ref.startsWith('git@') || ref.startsWith('ssh://') || ref.startsWith('git+https://')); } async resolve(ref) { if (!ref.rawUrl) return null; return { gitUrl: ref.rawUrl, ref: ref.version, label: ref.rawUrl, }; } async fetch(source, options = {}) { // Determine version let version = source.ref; if (!version) { version = await resolveLatestTag(source.gitUrl); } // Build cache key from URL const urlKey = source.gitUrl .replace(/^https?:\/\//, '') .replace(/^git@/, '') .replace(/\.git$/, '') .replace(/[:/]/g, '_'); const parts = urlKey.split('_'); const name = parts[parts.length - 1] ?? 'package'; const owner = parts[parts.length - 2] ?? 'unknown'; const cachePath = buildCachePath(owner, name, version); if (!options.refresh && existsSync(cachePath)) { return cachePath; } await mkdir(cachePath, { recursive: true }); if (existsSync(join(cachePath, '.git'))) { // Update existing clone await git(['fetch', '--tags', '--prune'], cachePath); } else { // Fresh clone (no --depth to get tags) await git(['clone', source.gitUrl, cachePath]); } // Checkout requested ref if (version && version !== 'latest') { await git(['checkout', version], cachePath); } return cachePath; } /** GitAdapter does not list packages */ async list() { return []; } } export { detectManifestType }; //# sourceMappingURL=git.js.map