Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import chalk from 'chalk';
import {
fetchRemote,
formatRemoteRef,
listCachedRemotes,
parseRemoteRef,
GitError,
} from '../../skills/remote.js';
import type { Reporter } from '../output/reporter.js';
import type { CLIOptions } from '../args.js';
/**
* Run the sync command.
* Updates cached remote skills to their latest versions.
* Pinned skills (with @sha) are skipped as they're immutable.
*/
export async function runSync(options: CLIOptions, reporter: Reporter): Promise<number> {
const cachedRemotes = listCachedRemotes();
if (cachedRemotes.length === 0) {
reporter.warning('No remote skills cached.');
reporter.tip('Add remote skills with: warden add --remote owner/repo --skill name');
return 0;
}
// If a specific remote is provided, only sync that one.
// Normalize to owner/repo so URL-form inputs match normalized state keys.
const targetRepo = options.remote;
let normalizedTarget: string | undefined;
if (targetRepo) {
try {
normalizedTarget = formatRemoteRef(parseRemoteRef(targetRepo));
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
reporter.error(message);
return 1;
}
}
const remotesToSync = normalizedTarget
? cachedRemotes.filter(({ ref }) => ref === normalizedTarget || ref.startsWith(`${normalizedTarget}@`))
: cachedRemotes;
if (targetRepo && remotesToSync.length === 0) {
reporter.error(`Remote not found in cache: ${targetRepo}`);
reporter.blank();
reporter.text('Cached remotes:');
for (const { ref } of cachedRemotes) {
reporter.text(` - ${ref}`);
}
return 1;
}
// Separate pinned and unpinned remotes
const unpinnedRemotes = remotesToSync.filter(({ ref }) => {
const parsed = parseRemoteRef(ref);
return !parsed.sha;
});
const pinnedRemotes = remotesToSync.filter(({ ref }) => {
const parsed = parseRemoteRef(ref);
return !!parsed.sha;
});
if (unpinnedRemotes.length === 0) {
if (pinnedRemotes.length > 0) {
reporter.warning('All cached remotes are pinned to specific versions.');
reporter.text('Pinned remotes do not need syncing as they are immutable.');
for (const { ref } of pinnedRemotes) {
reporter.text(` - ${ref} ${chalk.dim('(pinned)')}`);
}
} else {
reporter.warning('No remotes to sync.');
}
return 0;
}
reporter.bold('SYNC REMOTE SKILLS');
reporter.blank();
let updated = 0;
let errors = 0;
for (const { ref, entry } of unpinnedRemotes) {
reporter.step(`Syncing ${ref}...`);
const previousSha = entry.sha;
try {
const newSha = await fetchRemote(ref, { force: true });
if (newSha !== previousSha) {
reporter.success(`${ref}: updated ${chalk.dim(previousSha.slice(0, 7))} -> ${chalk.dim(newSha.slice(0, 7))}`);
updated++;
} else {
reporter.text(`${ref}: ${chalk.dim('already up to date')}`);
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
reporter.error(`${ref}: ${message}`);
if (err instanceof GitError && err.details?.kind === 'auth-required' && err.details.sshUrl) {
reporter.tip(`For private repos, the SSH URL form is: ${err.details.sshUrl}`);
}
errors++;
}
}
// Show pinned remotes that were skipped
if (pinnedRemotes.length > 0 && !targetRepo) {
reporter.blank();
reporter.text(chalk.dim('Skipped pinned remotes:'));
for (const { ref } of pinnedRemotes) {
reporter.text(chalk.dim(` - ${ref}`));
}
}
// Summary
reporter.blank();
if (errors > 0) {
reporter.warning(`Synced with ${errors} error${errors === 1 ? '' : 's'}.`);
return 1;
}
if (updated > 0) {
reporter.success(`Updated ${updated} remote${updated === 1 ? '' : 's'}.`);
} else {
reporter.success('All remotes up to date.');
}
return 0;
}
|