#!/usr/bin/env node /** * update CLI — Safely update the current branch from its base branch * (feature/release ← develop, hotfix ← main). Merge by default (ff when the * branch has no own commits), rebase opt-in. On conflict the operation is * aborted: the branch is left untouched. */ import { parseArgs } from 'node:util'; import { validate } from './validate.js'; import { execute } from './execute.js'; import { UpdateResultSchema } from './types.js'; async function main() { try { const args = parseArgs({ options: { spec: { type: 'string' }, workdir: { type: 'string' }, json: { type: 'boolean', default: false }, help: { type: 'boolean', short: 'h' }, }, allowPositionals: false, }); if (args.values.help) { console.log(` cli:gitflow-update — SmartStack Safely updates the CURRENT branch from its BASE branch (feature/release ← develop, hotfix ← main), using the fetched origin/ ref. Merge by default; on conflict the operation is aborted and the branch is left untouched. Usage: npx tsx index.ts --spec '' [--workdir ] [--json] Spec fields: strategy "merge" | "rebase" (default: "merge") push push the result to origin/ (rebase uses --force-with-lease) dryRun report the planned action without touching anything Options: --spec JSON input spec (required) --workdir Working directory for git operations (defaults to cwd) --json Output result as JSON --help, -h Show this help `); process.exit(0); } if (!args.values.spec) { console.error('Error: --spec is required'); process.exit(2); } let specObj: unknown; try { specObj = JSON.parse(args.values.spec); } catch { console.error('Error: --spec must be valid JSON'); process.exit(2); } const validation = validate(specObj); if (!validation.valid) { if (args.values.json) { console.log(JSON.stringify({ success: false, error: validation.blockers.join('; '), }, null, 2)); } else { console.error('Validation failed:'); validation.blockers.forEach(b => console.error(` - ${b}`)); } process.exit(2); } if (!args.values.json) { validation.warnings.forEach(w => console.warn(`[WARNING] ${w}`)); } const result = await execute(validation.data!, args.values.workdir); const validated = UpdateResultSchema.parse(result); if (args.values.json) { console.log(JSON.stringify(validated, null, 2)); } else { if (validated.success) { const head = validated.dryRun ? 'Dry-run' : 'Update'; if (validated.action === 'none') { console.log(`✓ ${validated.branch} is already up to date with ${validated.baseRef}`); } else { console.log(`✓ ${head}: ${validated.action} ${validated.baseRef} into ${validated.branch} (${validated.behind} commit(s) behind, ${validated.ahead} ahead)`); } if (validated.pushed) console.log(` Pushed to origin${validated.forcePushed ? ' (--force-with-lease)' : ''}`); } else { console.error(`✗ ${validated.error ?? 'update failed'}`); if (validated.conflictFiles && validated.conflictFiles.length > 0) { console.error(` Conflicts in: ${validated.conflictFiles.join(', ')}`); } if (validated.guidance) console.error(` ${validated.guidance}`); } (validated.warnings ?? []).forEach(w => console.warn(`[WARNING] ${w}`)); } process.exit(validated.success ? 0 : 1); } catch (err: unknown) { console.error('Fatal error:', (err as Error).message); process.exit(2); } } main();