@viraatdas/rudder 3.1.20 → 3.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ export interface CheckoutSyncReport {
2
+ root: string;
3
+ vcs: "jj" | "git";
4
+ branch: string;
5
+ fetched: boolean;
6
+ fetchError?: string;
7
+ behind: number;
8
+ ahead: number;
9
+ dirty: boolean;
10
+ moved: boolean;
11
+ note: string;
12
+ }
13
+ export declare function syncCheckout(root: string): Promise<CheckoutSyncReport>;
14
+ export declare function describeSync(where: string, report: CheckoutSyncReport): string;
@@ -0,0 +1,104 @@
1
+ // Bring one checkout up to date with GitHub, which is the source of truth
2
+ // between the laptop and the home machine: fetch, then move the checkout to the
3
+ // newest default branch only when that cannot lose or tangle work (nothing
4
+ // uncommitted, nothing unpushed). Anything else is reported, never forced.
5
+ //
6
+ // `rudder cloud sync` runs this here and asks the home machine to run the same
7
+ // code (`rudder __sync-checkout --json`) in its clone of the repo.
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import { runCommand } from "./util.js";
11
+ async function run(cwd, command, args) {
12
+ return await runCommand(command, args, { cwd, allowFailure: true });
13
+ }
14
+ async function defaultBranch(root) {
15
+ const head = await run(root, "git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
16
+ const name = head.code === 0 ? head.stdout.trim().replace(/^origin\//, "") : "";
17
+ if (name) {
18
+ return name;
19
+ }
20
+ for (const candidate of ["main", "master"]) {
21
+ if ((await run(root, "git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${candidate}`])).code === 0) {
22
+ return candidate;
23
+ }
24
+ }
25
+ return "main";
26
+ }
27
+ function count(text) {
28
+ return text.split("\n").filter((line) => line.trim()).length;
29
+ }
30
+ export async function syncCheckout(root) {
31
+ const vcs = fs.existsSync(path.join(root, ".jj")) ? "jj" : "git";
32
+ const fetch = vcs === "jj"
33
+ ? await run(root, "jj", ["git", "fetch"])
34
+ : await run(root, "git", ["fetch", "--prune", "origin"]);
35
+ const fetched = fetch.code === 0;
36
+ const fetchError = fetched ? undefined : (fetch.stderr || fetch.stdout).trim().split("\n").pop();
37
+ const branch = await defaultBranch(root);
38
+ const report = { root, vcs, branch, fetched, fetchError, behind: 0, ahead: 0, dirty: false, moved: false, note: "" };
39
+ return vcs === "jj" ? await syncJj(root, report) : await syncGit(root, report);
40
+ }
41
+ async function syncGit(root, report) {
42
+ const remote = `origin/${report.branch}`;
43
+ const current = (await run(root, "git", ["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
44
+ report.dirty = (await run(root, "git", ["status", "--porcelain"])).stdout.trim().length > 0;
45
+ const counts = await run(root, "git", ["rev-list", "--left-right", "--count", `HEAD...${remote}`]);
46
+ const [ahead, behind] = counts.stdout.trim().split(/\s+/).map((value) => Number(value) || 0);
47
+ report.ahead = ahead ?? 0;
48
+ report.behind = behind ?? 0;
49
+ if (report.behind === 0) {
50
+ const parts = [report.ahead > 0 ? `${report.ahead} unpushed commit(s) on ${current}` : "", report.dirty ? "uncommitted edits" : ""].filter(Boolean);
51
+ report.note = parts.length > 0 ? `up to date with ${report.branch}; ${parts.join(", ")}` : "up to date";
52
+ return report;
53
+ }
54
+ if (current !== report.branch) {
55
+ report.note = `on ${current}, not ${report.branch}; left alone`;
56
+ }
57
+ else if (report.dirty) {
58
+ report.note = `${report.behind} new commit(s) on ${report.branch}, but there are uncommitted changes; not pulled`;
59
+ }
60
+ else if (report.ahead > 0) {
61
+ report.note = `${report.branch} and this checkout have diverged (${report.ahead} local, ${report.behind} remote); not pulled`;
62
+ }
63
+ else {
64
+ const merge = await run(root, "git", ["merge", "--ff-only", remote]);
65
+ report.moved = merge.code === 0;
66
+ report.note = report.moved ? `pulled ${report.behind} new commit(s)` : `pull failed: ${(merge.stderr || merge.stdout).trim().split("\n").pop()}`;
67
+ }
68
+ return report;
69
+ }
70
+ async function syncJj(root, report) {
71
+ const trunk = `${report.branch}@origin`;
72
+ const jj = async (args) => await run(root, "jj", ["--ignore-working-copy", ...args]);
73
+ // Snapshot the working copy once so "empty" below is the truth.
74
+ await run(root, "jj", ["status"]);
75
+ const empty = (await jj(["log", "-r", "@", "--no-graph", "-T", 'if(empty, "1", "0")'])).stdout.trim() === "1";
76
+ report.dirty = !empty;
77
+ report.behind = count((await jj(["log", "-r", `::${trunk} ~ ::@`, "--no-graph", "-T", 'commit_id ++ "\\n"'])).stdout);
78
+ // Unpushed: committed changes below @ that the remote branch does not have
79
+ // (@ itself is the uncommitted working copy, reported as dirty).
80
+ report.ahead = count((await jj(["log", "-r", `(::@- ~ ::${trunk}) ~ empty()`, "--no-graph", "-T", 'commit_id ++ "\\n"'])).stdout);
81
+ if (report.behind === 0) {
82
+ const parts = [report.ahead > 0 ? `${report.ahead} unpushed change(s)` : "", report.dirty ? "uncommitted edits in the working copy" : ""].filter(Boolean);
83
+ report.note = parts.length > 0 ? `up to date with ${report.branch}; ${parts.join(", ")}` : "up to date";
84
+ return report;
85
+ }
86
+ if (!empty) {
87
+ report.note = `${report.behind} new commit(s) on ${report.branch}, but the working copy has uncommitted edits; not moved (\`jj rebase -d ${trunk}\` when ready)`;
88
+ }
89
+ else if (report.ahead > 0) {
90
+ report.note = `${report.behind} new commit(s) on ${report.branch}, and ${report.ahead} unpushed change(s) here; not moved`;
91
+ }
92
+ else {
93
+ // An empty, undescribed @ is abandoned by jj when the working copy moves.
94
+ const moved = await run(root, "jj", ["new", trunk]);
95
+ report.moved = moved.code === 0;
96
+ report.note = report.moved ? `moved to the newest ${report.branch} (${report.behind} new commit(s))` : `move failed: ${(moved.stderr || moved.stdout).trim().split("\n").pop()}`;
97
+ }
98
+ return report;
99
+ }
100
+ export function describeSync(where, report) {
101
+ const fetch = report.fetched ? "" : ` (fetch failed: ${report.fetchError ?? "unknown error"})`;
102
+ return `${where}: ${report.note}${fetch}`;
103
+ }
104
+ //# sourceMappingURL=checkout-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-sync.js","sourceRoot":"","sources":["../src/checkout-sync.ts"],"names":[],"mappings":"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"}
package/dist/cloud.js CHANGED
@@ -9,6 +9,7 @@ import { currentBranch, currentCommit, findRepoRoot } from "./git.js";
9
9
  import { applyDefaultDecisions, buildFreshHandoffPrompt, cloudWorkspaceAbsolutePath, findMigrationCandidates, formatCandidateReason, migrationSummary, summaryAsJson, } from "./migration.js";
10
10
  import { buildMoveBundle, findMoveCandidates, markRunsMovedHome } from "./move-home.js";
11
11
  import { cloudAuthPath } from "./state.js";
12
+ import { describeSync, syncCheckout } from "./checkout-sync.js";
12
13
  import { ensureDir, commandExists, expandHome, isTty, newRunId, nowIso, pathExists, promptConfirm, promptText, promptSelect, promptSecret, readJson, runCommand, shortenHome, shellQuote, writeJson, } from "./util.js";
13
14
  const DEFAULT_LOGIN_INTERVAL_MS = 2000;
14
15
  const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
@@ -134,6 +135,9 @@ export async function runCloudCommand(command, args, options = {}) {
134
135
  case "doctor":
135
136
  await cloudDoctor(options);
136
137
  return;
138
+ case "sync":
139
+ await cloudSync(rest, options);
140
+ return;
137
141
  case "app":
138
142
  case "pair":
139
143
  await printAppLink(options);
@@ -4135,6 +4139,83 @@ async function reportCredentialParity(client, { problemsOnly }) {
4135
4139
  }
4136
4140
  return problems.length;
4137
4141
  }
4142
+ // `rudder cloud sync` (and `/sync` in the dashboard): GitHub is the meeting
4143
+ // point, so this checkout and the home machine's clone each fetch and move to
4144
+ // the newest default branch when that is safe, and the vault's copy of this
4145
+ // laptop's config is brought level. Unpushed or uncommitted work on either side
4146
+ // is reported, never pushed or overwritten. `--all` syncs every repo open on
4147
+ // the home machine.
4148
+ async function cloudSync(args, options) {
4149
+ const client = await cloudClient({ requireToken: true });
4150
+ const all = args.includes("--all");
4151
+ const root = findRepoRoot();
4152
+ const slug = await githubSlugFromOrigin(root);
4153
+ const lines = [];
4154
+ let local = null;
4155
+ if (slug) {
4156
+ local = await syncCheckout(root);
4157
+ lines.push(describeSync(`this laptop (${slug})`, local));
4158
+ }
4159
+ else if (!all) {
4160
+ throw new Error("This repo has no GitHub origin, so there is nothing on the home machine to sync it with. `rudder cloud sync --all` syncs every repo there.");
4161
+ }
4162
+ await ensureVaultAws(client).catch((error) => {
4163
+ lines.push(`config: could not update the vault (${error instanceof Error ? error.message : String(error)})`);
4164
+ });
4165
+ if (!options.json) {
4166
+ process.stderr.write("Asking the cloud machine (waking it if it is asleep)...\n");
4167
+ }
4168
+ const cloud = await client.request("/api/rudder/home/sync", {
4169
+ method: "POST",
4170
+ body: all || !slug ? {} : { repos: [slug] },
4171
+ });
4172
+ const results = cloud.results ?? [];
4173
+ if (options.json) {
4174
+ printJson({ local, cloud: results });
4175
+ return;
4176
+ }
4177
+ for (const result of results) {
4178
+ lines.push(result.report ? describeSync(`cloud (${result.repo})`, result.report) : `cloud (${result.repo}): ${result.error ?? "no answer"}`);
4179
+ }
4180
+ if (slug && results.length === 0) {
4181
+ lines.push(`cloud: ${slug} is not open on the cloud machine (\`rudder cloud\` opens it there)`);
4182
+ }
4183
+ // The agents, on each side. Cloud ones are listed from what the home machine
4184
+ // last reported; laptop ones that are still running are what /sync move (or H
4185
+ // in the dashboard) would hand over.
4186
+ const homeView = await client.request("/api/rudder/home", { method: "GET" }).catch(() => null);
4187
+ for (const project of homeView?.projects ?? []) {
4188
+ if (!project.repo || !results.some((result) => result.repo.toLowerCase() === project.repo.toLowerCase())) {
4189
+ continue;
4190
+ }
4191
+ lines.push(...describeCloudAgents(project.repo, project.agents ?? []));
4192
+ }
4193
+ if (slug) {
4194
+ const running = await findMoveCandidates(root).catch(() => []);
4195
+ if (running.length > 0) {
4196
+ lines.push(`${running.length} agent(s) still running on this laptop: ${running.slice(0, 4).map((run) => `"${(run.taskSummary || run.task).slice(0, 50)}"`).join(", ")}${running.length > 4 ? ", ..." : ""}. They stop when the laptop sleeps; \`/sync move\` (or H) moves them to the cloud machine.`);
4197
+ }
4198
+ }
4199
+ const unpushed = [local, ...results.map((result) => result.report)].filter((report) => !!report && report.ahead > 0);
4200
+ if (unpushed.length > 0) {
4201
+ lines.push("Unpushed work stays where it is: push it (or merge its PR) and run /sync again to bring the other side up.");
4202
+ }
4203
+ console.log(lines.join("\n"));
4204
+ }
4205
+ function describeCloudAgents(repo, agents) {
4206
+ const live = agents.filter((agent) => !["merged", "stopped", "deleted"].includes(String(agent.status)));
4207
+ if (live.length === 0) {
4208
+ return [`cloud agents (${repo}): none running`];
4209
+ }
4210
+ const label = (agent) => (Array.isArray(agent.asks) && agent.asks.length > 0 ? "needs you" : String(agent.status || "?"));
4211
+ const order = ["needs you", "waiting", "working", "starting", "review", "paused", "done", "failed"];
4212
+ const sorted = [...live].sort((a, b) => (order.indexOf(label(a)) + 1 || 99) - (order.indexOf(label(b)) + 1 || 99));
4213
+ return [
4214
+ `cloud agents (${repo}): ${live.length}`,
4215
+ ...sorted.slice(0, 8).map((agent) => ` ${label(agent).padEnd(10)} ${(agent.title || "").slice(0, 70)}`),
4216
+ ...(sorted.length > 8 ? [` ... and ${sorted.length - 8} more (\`rudder cloud\` to open them)`] : []),
4217
+ ];
4218
+ }
4138
4219
  async function cloudDoctor(options) {
4139
4220
  const client = await cloudClient({ requireToken: true });
4140
4221
  if (options.json) {