storebound/cli/lib/diff.ts
CRT b63f2fdfbc Store operations: TestFlight wait / expire, IAP review submissions, listing pulls, release notes per language, offers, custom product pages, in-app events, reports, triage, keyword ranks, AdAttributionKit scan
- apple testflight wait --build N (progress, timeout, exit 0/1/7) and expire --keep N | --older-than D (dry run, asks)
- apple iap-submit: IAP / subscription / group versions into a review submission; first of a type only with the app
  version (--with-version), --no-submit keeps the draft; read-only MCP plan
- apple listing pull, play listing pull (diff, --write), catalog pull (config proposal, never rewritten)
- whats-new --languages / check / apply: one file per language, agent or hook translation, per-store limits
- apple offer-codes, win-back, promoted, preorder; Play pre-registration as a guide (no API)
- apple cpp and apple events: list, create, upload, submit (asks)
- reports: Apple sales / finance, Play earnings / sales into .storebound/reports with summary and CSV
- triage: TestFlight crash / feedback and Play error reports grouped by signature, device, OS
- aso rank: iTunes Search API ranks and competitors, rate limited
- ads scan: AdAttributionKit / SKAdNetwork Info.plist and source checks
- tests against recorded, sanitized answers; skills, README, CHANGELOG
2026-10-11 23:58:17 +08:00

32 lines
1.6 KiB
TypeScript

// Line diff for "pull" commands (live store text vs the local files). Pure, no dependency: longest common subsequence
// over lines, printed like a unified diff without hunks (" " same, "-" only local, "+" only on the store).
export function lineDiff(before: string, after: string): string[] {
const a = before.split('\n'), b = after.split('\n');
if (a.length * b.length > 4_000_000) return [...a.map(l => `-${l}`), ...b.map(l => `+${l}`)];
const n = a.length, m = b.length;
const lcs: Uint32Array[] = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) lcs[i]![j] = a[i] === b[j] ? lcs[i + 1]![j + 1]! + 1 : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!);
const out: string[] = [];
let i = 0, j = 0;
while (i < n && j < m) {
if (a[i] === b[j]) { out.push(` ${a[i]}`); i++; j++; }
else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) out.push(`-${a[i++]}`);
else out.push(`+${b[j++]}`);
}
while (i < n) out.push(`-${a[i++]}`);
while (j < m) out.push(`+${b[j++]}`);
return out;
}
/** Only the changed lines with `context` unchanged lines around them ("..." between gaps). Empty when equal. */
export function compactDiff(before: string, after: string, context = 2): string[] {
const d = lineDiff(before, after);
if (!d.some(l => l[0] !== ' ')) return [];
const keep = new Set<number>();
d.forEach((l, i) => { if (l[0] !== ' ') for (let k = i - context; k <= i + context; k++) keep.add(k); });
const out: string[] = [];
let last = -1;
d.forEach((l, i) => { if (!keep.has(i)) return; if (last >= 0 && i > last + 1) out.push('...'); out.push(l); last = i; });
return out;
}